CoreData: EXC_BAD_INSTRUCTION on private queue with Swift - ios

I get EXC_BAD_INSTRUCTION error when I try to save context (second call to privateManagedObjectContext.hasChanges method).
Here is my method to save context:
func saveContext(#wait: Bool, completion: ((error: NSError?) -> ())?) {
if mainManagedObjectContext.hasChanges || privateManagedObjectContext.hasChanges {
if mainManagedObjectContext.hasChanges {
mainManagedObjectContext.performBlockAndWait({ () -> Void in
var error: NSError? = nil
if !self.mainManagedObjectContext.save(&error) {
println("Error saving main managed object context \(error)")
if completion != nil {
completion!(error: error)
}
return
}
})
}
let saveBlock: () -> Void = {
var error: NSError? = nil
if self.privateManagedObjectContext.save(&error) {
if completion != nil {
completion!(error: nil)
}
} else {
println("Error saving private managed object context \(error)")
if completion != nil {
completion!(error: error)
}
}
}
if self.privateManagedObjectContext.hasChanges {
if (wait) {
self.privateManagedObjectContext.performBlockAndWait(saveBlock)
} else {
self.privateManagedObjectContext.performBlock(saveBlock)
}
return
}
if completion != nil {
completion!(error: nil)
}
} else {
// No changes
if completion != nil {
completion!(error: nil)
}
}
}
I declared contexts like so:
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedObjectContext.persistentStoreCoordinator = storeCoordinator
mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainManagedObjectContext.parentContext = privateManagedObjectContext
What did I wrong here?

Related

Why do I have a crash while saving parent context?

func save(handler: #escaping (NSManagedObjectContext) -> Void, completion: ((Error?)-> Void)? = nil) {
if let rootContext = rootContext {
let localContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
localContext.parent = rootContext
localContext.obtainPermanentIdsBeforeSaving()
handler(localContext)
guard localContext.hasChanges else {
DispatchQueue.main.async {
completion?(nil)
}
return
}
do {
try localContext.save()
try localContext.parent?.save() //here, line 65 in CoreDataManager
DispatchQueue.main.async {
completion?(nil)
}
} catch let error as NSError {
print("❌ Core Data Save Error \(error), \(error.userInfo)")
DispatchQueue.main.async {
completion?(error)
}
}
}
}
This is report from Firebase:
Please note that I am not an expert on CoreData, however to me, it looks like a threading issue.
A simple solution that you could try would be to wrap your parent context's save call in the performAndWait(_:) method, like so:
func save(handler: #escaping (NSManagedObjectContext) -> Void, completion: ((Error?)-> Void)? = nil) {
if let rootContext = rootContext {
let localContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
localContext.parent = rootContext
localContext.obtainPermanentIdsBeforeSaving()
handler(localContext)
guard localContext.hasChanges else {
DispatchQueue.main.async {
completion?(nil)
}
return
}
do {
try localContext.save()
localContext.parent?.performAndWait {
try localContext.parent?.save()
}
DispatchQueue.main.async {
completion?(nil)
}
} catch let error as NSError {
print("❌ Core Data Save Error \(error), \(error.userInfo)")
DispatchQueue.main.async {
completion?(error)
}
}
}
}

What is the proper way to work with AWSTask objects in Swift?

Hello and thanks in advance for your time.
In my code I am making various requests to AWSSQS which all return AWSTask. I have found working with these AWSTask objects to be very difficult while also trying to keep all the logic specific to AWS in a single class so I can easily switch to a different cloud service if need be.
Ideally, what I would like to do is execute a series of AWS tasks asynchronously in a serial fashion. Normally I would just add tasks to a custom Serial Dispatch Queue but since The AWSTask objects are themselves asynchronous tasks, I can't do that.
Here is a simple example that illustrates the problem I am having. It doesn't have any real world purpose but it does a good job illustrating the problem. Below, I have code to create a SQS queue, send a message to a SQS queue, receive a message from an SQS queue, and delete a SQS queue. Let's say I want to do those four things in a serial, asynchronous fashion. In other words, I want to make sure the previous task succeeded before attempting the next task.
ViewController
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
awsClass.runTest()
DispatchQueue.main.async {
print("Test Finished")
}
}
AwsClass
public func createQueue(){
guard let createQueueRequest = AWSSQSCreateQueueRequest() else{fatalError()}
createQueueRequest.queueName = "TestQueue"
sqs.createQueue(createQueueRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
self.queueUrl = task.result!.queueUrl!
print("created queue at: \(self.queueUrl!)")
}
return nil
})
}
public func deleteQueue(){
if queueUrl != nil {
guard let deleteQueueRequest = AWSSQSDeleteQueueRequest() else{fatalError()}
deleteQueueRequest.queueUrl = queueUrl
sqs.deleteQueue(deleteQueueRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("queue sucessfully deleted from \(self.queueUrl!)")
self.queueUrl = nil
}
return nil
})
}
else{
print("Queue has already been deleted")
}
}
public func sendMessage(messageData: String, toConnectId: String) {
guard let sendMessageRequest = AWSSQSSendMessageRequest() else{fatalError()}
sendMessageRequest.queueUrl = toConnectId
sendMessageRequest.delaySeconds = 0
sendMessageRequest.messageBody = messageData
sqs.sendMessage(sendMessageRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("successfully sent message to \(toConnectId)")
}
return nil
})
}
public func receiveMessage(){
guard let receiveMessageRequest = AWSSQSReceiveMessageRequest() else{fatalError()}
receiveMessageRequest.queueUrl = self.queueUrl
receiveMessageRequest.maxNumberOfMessages = 1
sqs.receiveMessage(receiveMessageRequest).continueWith(block: {(task) -> AnyObject? in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
let message = (task.result?.messages?.first)!
print("successfully received message with body: \(message.body ?? "failed")")
}
return nil
})
}
public func runTest(){
let mySerialQueue = DispatchQueue(label: "mySerialQueue")
mySerialQueue.sync {
self.createQueue()
}
mySerialQueue.sync {
self.sendMessage(messageData: "test", toConnectId: "https://someUrl")
}
mySerialQueue.sync {
self.receiveMessage()
}
mySerialQueue.sync {
self.deleteQueue()
}
}
Since the AWSTasks are asynchronous with completion functions, the code quickly makes all four calls and then the completion functions are called whenever those tasks finish. Instead I want the completion function of the first task to finish before the next task begins.
The AWSTask objects are meant to be "chained" together.
Documentation can be found here: http://docs.aws.amazon.com/mobile/sdkforios/developerguide/awstask.html
A small example here:
sqs.createQueue(/* parameters */).continueWithSuccess(block: {(task) -> Void in
// Success
return sqs.sendMessage(/* parameters */)
}).continueWithSuccess(block: {(task) -> Void in
// Success
return sqs.receiveMessage(/* parameters */)
}).continueWithSuccess(block: {(task) -> Void in
// Success
return sqs.deleteQueue(/* parameters */)
})
Alright, so I found a solution to my question. It works exactly as desired but it does so in this nasty chain of completion functions. If anyone knows a more elegant solution, I am all ears!
ViewController
print("Starting Test")
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
atomConnector.runTest(completion: {
print("test finshed")
})
}
AwsClass
public func createQueue(completion: #escaping () -> Void){
guard let createQueueRequest = AWSSQSCreateQueueRequest() else{fatalError()}
createQueueRequest.queueName = "TestQueue"
sqs.createQueue(createQueueRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
self.queueUrl = task.result!.queueUrl!
print("created queue at: \(self.queueUrl!)")
completion()
}
})
}
public func deleteQueue(completion: #escaping () -> Void){
if queueUrl != nil {
guard let deleteQueueRequest = AWSSQSDeleteQueueRequest() else{fatalError()}
deleteQueueRequest.queueUrl = queueUrl
sqs.deleteQueue(deleteQueueRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("queue sucessfully deleted from \(self.queueUrl!)")
self.queueUrl = nil
completion()
}
})
}
else{
print("Queue has already been deleted")
}
}
public func sendMessage(messageData: String, toConnectId: String, completion: #escaping () -> Void) {
guard let sendMessageRequest = AWSSQSSendMessageRequest() else{fatalError()}
sendMessageRequest.queueUrl = toConnectId
sendMessageRequest.delaySeconds = 0
sendMessageRequest.messageBody = messageData
sqs.sendMessage(sendMessageRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("successfully sent message to \(toConnectId)")
completion()
}
})
}
public func receiveMessage(completion: #escaping () -> Void){
guard let receiveMessageRequest = AWSSQSReceiveMessageRequest() else{fatalError()}
receiveMessageRequest.queueUrl = self.queueUrl
receiveMessageRequest.maxNumberOfMessages = 1
sqs.receiveMessage(receiveMessageRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
let message = (task.result?.messages?.first)!
print("successfully received message with body: \(message.body ?? "failed")")
self.deleteMessage(receiptHandle: message.receiptHandle, completion: completion)
}
})
}
public func deleteMessage(receiptHandle: String?, completion: #escaping () -> Void){
guard let deleteMessageRequest = AWSSQSDeleteMessageRequest() else{fatalError()}
deleteMessageRequest.queueUrl = self.queueUrl
deleteMessageRequest.receiptHandle = receiptHandle
sqs.deleteMessage(deleteMessageRequest).continueWith(block: {(task) -> Void in
if task.error != nil {
print(task.error!)
}
else if task.result != nil {
print("successfully deleted message with receiptHandle: \(receiptHandle)")
completion()
}
})
}
public func runTest(completion: #escaping () -> Void){
self.createQueue(completion: {
self.sendMessage(messageData: "test", toConnectId: "https://someUrl", completion: {
self.receiveMessage(completion: {
self.deleteQueue(completion: {
completion()
})
})
})
})
}

How to fetch all pending notifications from iCloud and notify it that my app received notification?

Usually I receive notifications immediately when record changes.
But what to do when my app is not running, and I need to fetch all pending notifications?
Simply do it like this:
func fetchPendingNotifications() {
var queryNotifications = [CKQueryNotification]()
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: nil)
let operationQueue = OperationQueue()
operation.notificationChangedBlock = { notification in
if let queryNotification = notification as? CKQueryNotification {
queryNotifications.append(queryNotification)
}
}
operation.fetchNotificationChangesCompletionBlock = { _, error in
if error == nil {
self.perform(queryNotifications: queryNotifications) { _ in
//do sth on success
}
}
}
operationQueue.addOperation(operation)
}
the helper function is:
private func perform(queryNotifications: [CKQueryNotification], completion: ErrorHandler? = nil) {
var currentQueryNotifications = queryNotifications
if let queryNotification = currentQueryNotifications.first {
currentQueryNotifications.removeFirst()
if queryNotification.queryNotificationReason == .recordCreated || queryNotification.queryNotificationReason == .recordUpdated {
//fetch the record from cloud and save to core data
self.fetchAndSave(with: queryNotification.recordID!) { error in
if error == nil {
OperationQueue().addOperation(CKMarkNotificationsReadOperation(notificationIDsToMarkRead: [queryNotification.notificationID!]))
self.perform(queryNotifications: currentQueryNotifications, completion: completion)
} else {
completion?(error)
}
}
} else {
//delete record from coredata
self.delete(with: queryNotification.recordID!) { error in
if error == nil {
OperationQueue().addOperation(CKMarkNotificationsReadOperation(notificationIDsToMarkRead: [queryNotification.notificationID!]))
self.perform(queryNotifications: currentQueryNotifications, completion: completion)
} else {
completion?(error)
}
}
}
} else {
completion?(nil)
}
}

dispatch_group_notify has been replaced by instance method DispatchGroup.notify(qos:flags:queue:execute:)

I am trying to migrate my swift2.2 codebase to swift3. Beforehand, for GCD, this code used to work for me. But it shows an error of dispatch_group_notify has been replaced by instance method DispatchGroup.notify(qos:flags:queue:execute:)
How can I tackle this error?
dispatch_group_notify(group, dispatch_get_main_queue()) {
if productsError != nil || citiesError != nil || usersError != nil {
completionHandler(false)
} else {
completionHandler(true)
}
}
This is the new code I am writing and I am stuck while I am leaving the group.
class APIHandler {
func requestDataFromTheServerWithCompletionhandler(completionHandler: ((Bool) -> Void)) {
let group = DispatchGroup()
var productsError: NSError?
var citiesError: NSError?
var usersError: NSError?
var categoriesError: NSError?
let manager = SessionManager()
// MARK:- Products
group.enter()
let productsParser = ProductsParser()
let productsURL = URLs.productsURL
manager.requestDataWithCompetionHandler(urlString: productsURL) {( responseData, error) in
if responseData != nil {
print("Products Success")
productsParser.parseAndStoreProductsData(dataToParse: responseData!)
}
else {
print("Products Error")
productsError = error
}
group.leave()
}
dispatch_group_notify(group, DispatchQueue.main) {
if productsError != nil || citiesError != nil || usersError != nil {
completionHandler(false)
} else {
completionHandler(true)
}
}
}
}
I think you have combine old and new both way...
dispatch_group_notify is old code.
Try as follows :
DispatchGroup().notify(queue: DispatchQueue.main) {
}
Through the variable you can also use like as :
let dispatchGroup = DispatchGroup()
dispatchGroup.notify(queue: DispatchQueue.main) {
}
And also as compiler displayed you can use as follows :
dispatchGroup.notify(qos: DispatchQoS.background, flags: DispatchWorkItemFlags.assignCurrentContext, queue: DispatchQueue.main) {
}
Hope it helps you.

CoreData memory not released after importing data

I have an issue with importing data to a coredata project with swift.
I'm importing a lot of images, which results in quite a big memory footprint (around 100 - 120MB). The problem is, that once the images are downloaded and imported, I save the managedObjectContext and reset it, but the memory is not released. If however, I send the app to the background, most of the memory is released (I end up with ~50MB).
I use the following CoreData setup:
one masterContext with PrivateQueueConcurrencyType
one mainContext with MainQueueConcurrencyType and masterContext as parentContext
the import runs on it's own context with PrivateQueueConcurrencyType and the mainContext as it's parentContext
the following method starts the import:
func downloadProductImages(completion: (error: NSError?) -> Void) {
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
var err: NSError? = nil
self.importBrandImages({ (error) -> Void in
if error != nil {
completion(error: error)
return
}
moc.save(nil)
self.importProductVariantThumbnails({ (error) -> Void in
if error != nil {
completion(error: error)
return
}
moc.save(nil)
moc.reset()
self.backgroundMOC = nil
completion(error: err)
})
})
}
}
}
and these methods download the images and save them in the database:
private func importBrandImages(completion: (error: NSError?) -> Void) {
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
var error: NSError? = nil
let brandsFetchReq = NSFetchRequest(entityName: "Brand")
// brand images
if let brands = moc.executeFetchRequest(brandsFetchReq, error: &error) as? [Brand] {
let imageQueue = TaskQueue()
autoreleasepool({ () -> () in
for brand in brands {
if let logoSrc = brand.logoSrc {
imageQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(logoSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
brand.logo = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
if let bgImgSrc = brand.bgImageSrc {
imageQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(bgImgSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
brand.bgImage = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
}
})
imageQueue.run(completion: { () -> Void in
moc.save(nil)
completion(error: error)
})
} else {
completion(error: error)
}
}
}
}
private func importProductVariantThumbnails(completion: (error: NSError?) -> Void) {
var err: NSError? = nil
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
let pVariantsFetchReq = NSFetchRequest(entityName: "ProductVariant")
if let variants = moc.executeFetchRequest(pVariantsFetchReq, error: &err) as? [ProductVariant] {
let importQueue = TaskQueue()
autoreleasepool({ () -> () in
for variant in variants {
if let thumbnailSrc = variant.thumbnailSrc {
importQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(thumbnailSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
variant.thumbnail = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
}
})
importQueue.run { () -> Void in
moc.save(nil)
self.importProductVariantImages({ (error) -> Void in
completion(error: error)
})
}
}
}
}
}
private func importProductVariantImages(completion: (error: NSError?) -> Void) {
var error: NSError? = nil
if let moc = self.backgroundMOC {
moc.performBlock { () -> Void in
moc.reset()
let pImagesFetchReq = NSFetchRequest(entityName: "ProductImage")
// product images
if let images = moc.executeFetchRequest(pImagesFetchReq, error: &error) as? [ProductImage] {
let importQueue = TaskQueue()
autoreleasepool({ () -> () in
for pImage in images {
if let imageSrc = pImage.imageSrc {
importQueue.tasks +=~ { results, next in
ImageLoader.sharedLoader.imageForUrl(imageSrc.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!, completionHandler: { (image, url) -> () in
if image != nil {
pImage.imageData = UIImageJPEGRepresentation(image, 0.35)
}
next(nil)
})
}
}
}
})
importQueue.run(completion: { () -> Void in
moc.save(nil)
completion(error: error)
})
} else {
completion(error: error)
}
}
}
}
I have no idea, why the memory is not released. I used Instruments to find memory leaks, but it doesn't show any.
Your problem appears to be that ImageLoader contains a cache and is adding all of the images to it. If you don't need that functionality, and it doesn't really look like you do, then you should delete it and simplify the ImageLoader so it just downloads and returns the images.

Resources