1

I have a section of code where it firstly empties out a Core Data table, then fills it back up with new and updated data. This code is inside a DispatchQueue.main.async block, that occurs after an asynchronous request has been completed. This is the relevant code from the function.

Here is my code:

let queue = OperationQueue() let deleteOperation = BlockOperation { let fetchRequest = Track.fetchRequest() let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) batchDeleteRequest.resultType = .resultTypeCount do { try self.managedObjectContext.execute(batchDeleteRequest) self.managedObjectContext.reset() } catch { fatalCoreDataError(error) self.debug.log(tag: "RecentSessionsViewController", content: "Failed to delete cache") return } } let addOperation = BlockOperation { for track in tempTracks { let masterListEntry = NSEntityDescription.insertNewObject(forEntityName: "Track", into: self.managedObjectContext) as! Track masterListEntry.id = track["id"] as! NSNumber masterListEntry.name = track["name"] as! String masterListEntry.comment = track["comment"] as! String do { try self.managedObjectContext.save() self.trackMasterList.append(masterListEntry) } catch { self.debug.log(tag: "RecentSessionsViewController", content: "Core data failed") fatalError("Error: \(error)") } } } addOperation.addDependency(deleteOperation) queue.addOperations([deleteOperation, addOperation], waitUntilFinished: false) 

I have random crashes from time to time on the addOperation block of code for some reason. In the most recent crash, an exception breakpoint has been trigger on:

try self.managedObjectContext.save() 

If I hit continue, it lands on this breakpoint again, then when I hit continue again, it crashes showing assembly and this error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil' *** First throw call stack: ( 0 CoreFoundation 0x000000010c70f34b __exceptionPreprocess + 171 1 libobjc.A.dylib 0x000000010bd5321e objc_exception_throw + 48 2 CoreFoundation 0x000000010c778265 +[NSException raise:format:] + 197 3 CoreFoundation 0x000000010c6a762b -[__NSCFSet addObject:] + 155 4 CoreData 0x000000010c2427ff -[NSManagedObjectContext(_NSInternalChangeProcessing) _processPendingUpdates:] + 399 5 CoreData 0x000000010c23cccd -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 2445 6 CoreData 0x000000010c240e0c -[NSManagedObjectContext save:] + 412 7 AppName 0x000000010b0db7eb _TFFFFC8AppName28RecentSessionsViewController22refreshListFT_T_U_FTGSqV10Foundation4Data_GSqCSo11URLResponse_GSqPs5Error___T_U_FT_T_U0_FT_T_ + 6459 8 AppName 0x000000010b086987 _TTRXFo___XFdCb___ + 39 9 Foundation 0x000000010b8572cd __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7 10 Foundation 0x000000010b856faf -[NSBlockOperation main] + 101 11 Foundation 0x000000010b8556ac -[__NSOperationInternal _start:] + 672 12 Foundation 0x000000010b8515ef __NSOQSchedule_f + 201 13 libdispatch.dylib 0x00000001134100cd _dispatch_client_callout + 8 14 libdispatch.dylib 0x00000001133ede6b _dispatch_queue_serial_drain + 236 15 libdispatch.dylib 0x00000001133eeb9f _dispatch_queue_invoke + 1073 16 libdispatch.dylib 0x00000001133f13b7 _dispatch_root_queue_drain + 720 17 libdispatch.dylib 0x00000001133f108b _dispatch_worker_thread3 + 123 18 libsystem_pthread.dylib 0x00000001137bf746 _pthread_wqthread + 1299 19 libsystem_pthread.dylib 0x00000001137bf221 start_wqthread + 13 ) libc++abi.dylib: terminating with uncaught exception of type NSException 

In the past, I've also seen it crash with the following error: "attempt to recursively call -save: on the context aborted".

Any ideas what is going on? The whole reason I wrapped these things into NSOperation blocks was in an attempt to prevent this. Clearly that didn't work. This is sporadic, so I'm never sure if its been solved or not.

I'm guessing what is happening is that its still in the process of deleting data, when it tries to write data.

2

1 Answer 1

2

You're not doing Core Data concurrency correctly. The right way is to use the perform and performAndWait methods on NSManagedObjectContext. Using a context in an operation queue like your code does is a recipe for concurrency related bugs.

Dispatch queues don't help you here. You can still use dispatch queues, but you must also use Core Data's concurrency methods to avoid problems.

Sign up to request clarification or add additional context in comments.

4 Comments

Ah. So for example, on the addOperation block (i'll remove the NSOperations stuff), do I wrap the whole for loop in a performBlockAndWait, or just the try catch?
I've basically switch the block operation definition lines out for a self.managedObjectContext.performAndWait in both instances. this appears to work. Similar to performAndWait I noticed there is a perform on managedObjectContext. Why would you use perform? Why not just write it how I've done above.
The "perform" methods need to wrap everything that touches Core Data in any way. You'd use perform if you can continue doing other work while Core Data does its job on its own queue.
Ah, that makes sense. By wrap everything that touches Core Data, this includes NSEntityDescription.insertNewObject(forEntityName: "Track", into: self.managedObjectContext) as! Track right?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.