Is the save: method thread-safe? - ios

In short
Can I call
[moc performBlockAndWait:^{
[moc save:NULL] ;
}];
from different threads at the same time?
In long
I add a crash similar to this one, namely:
Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2011-06-15 11:36:59.864 myApp[457:607] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
*** Call stack at first throw:
The program crashes on this command:
[moc performBlockAndWait:^{
[moc save:NULL] ;
}];
As I launch the same process (with difference parameters) to as many threads as possible (with the help of NSOperationQueue), this command might be called by different threads as the same time.
Could that be a problem? Or the method performBlockAndWait: already deals with that?
I ask you the question to know if I need to create a singleton that would manage the saving to the moc.

-save: must be called from the thread/queue that created the context.
calling -performBlockAndWait: does avoid calling it from the wrong thread.
You must pass in a NSError to the -save: method and you need to watch the result of the -save: to determine if an error is occurred. That is your singular way of knowing if an error occurred. Passing in NULL is asking for trouble.
The error you are currently seeing is not caused from the save directly. It is most likely caused because you are listening to NSManagedObjectContextDidSaveNotification somewhere else and doing something incorrect there. Search your code for that constant and review the code associated with it.

Related

iOS Core Data - Serious application error - attempt to insert nil - in less than 1%

iOS Core Data - Serious application error - attempt to insert nil
Hello,
My app runs actualy stable, but in seldom cases it crashes with this error message...
2019-04-02 20:48:52.437172+0200 myAppName[4422:1595677] [error] error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2019-04-02 20:48:52.438246+0200 myAppName[4422:1595677] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
...when it tries to save the current context (this part in my code is still in objc):
- (void)saveChanges
{
dispatch_async(dispatch_get_main_queue(), ^{
NSError *err = nil;
BOOL succesful = [self->context save:&err];
if (!succesful)
{
NSLog(#"ERROR MESSAGE DURING SAVING CONTEXT: %#", [err localizedDescription]);
}
});
}
'seldom' means:
Most Customers do never experience the issue, for few customers it happens several times per day.
I was able to produce it 2 times during the last two days although I tried several ways to force this error (see below).
This is the setup:
The respective data is in one Entity (table)
A NSFetchedResultsController shows the data in an UITableView
User can hit a button to add a new record.
New record has only some basic data and initiates two API calls to two webservers
Each webserver response does update the record
After both are done (or were cancelled due to timeout), I call the saveChanges function from above only once.
All functions use the same context created by NSPersistentContainer as follow (this part is already in swift)
#objc lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "myAppName")
let description = NSPersistentStoreDescription(url: SomeHelper.urlForFileInDocFolder("storev7.data"))
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
If I could reproduce the error somehow, I could find an appropriate solution, but as it almost never happens, I'm stuck.
Do you have an idea how I could reproduce the error from above? Or do you have a clue what could cause the error in my case?
What I tried already to reproduce the error:
Create hundereds of record
Create hundereds of record in a few seconds
Create hundereds of record during switching internet connection on / off / on / off /...
Create hundereds of record during mixed from background and main thread (I removed the dispatch from saveChanges for that)
Create hundereds of record with different delays on the API (added random sleep function on the webserver)
Long time execution, the app run for 24 hours on a real device and created record each 2 minutes
Mixes of all of them
NSManagedObjects are restricted to a single queue. They are not thread-safe for reading or writing. Reading an NSManagedObject can cause a fault, which is a write operation. That means that NSManagedObjects retrieved from a main queue context (like viewContext) cannot be passed to other queues.
The details of all of this are discussed in the Core Data Programming Guide:
NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.
The general approach with NSPersistentContainer is to use something like viewContext exclusively on the main queue, and to use performBackgroundTask to handle background operations, or you can use newBackgroundContext to generate a background context, and use perform or performAndWait on it to manipulate objects that are fetched from that context.
Moving an object between contexts is done by fetching the same objectID in the other context (keeping in mind that this will return a fresh instance from the store).
You can track down mistakes by adding -com.apple.CoreData.ConcurrencyDebug 1 to your scheme. When you do this, errors will immediately trap on the delightfully named __Multithreading_Violation_AllThatIsLeftToUsIsHonor__.

Realm throwing RLMException when checking equality on property

Whenever I try to check equality between a property on a Realm object and a NSInteger, it throws the following exception:
*** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
However I am not modifying the object, just accessing it. Do I have to start a write transaction to check equality?
If I put a breakpoint right at the start of the if statement, the next step in throws the exception.
//message is a RLMObject stored in a RLMResults array
if (message.status == 3 || message.status == 4) {
NSLog(#"Message status: %ld", (long)message.status);
}
The issue was that I was querying and modifying the RLMObject elsewhere in the setup of another view controller. This was causing the exception to be thrown. My fault for not looking closely at what my other code was doing.

Saving NSManagedObjectContext on Stackmob -- uploads to server, but crashes app

I have been running into an issue in using StackMob as the backend of my iOS application (though I'm not sure if this is an issue in wrongly using StackMob's methods or an iOS issue).
I am allowing a user to create a post object that is just a subclassed NSManagedObject, and uploading that to the server to be used in other parts of the application. The issue that arises occurs in the method:
[NSManagedObjectContext saveOnSuccess:<^(void)successBlock> onFailure:<^(NSError *error)failureBlock>];
Here, I am using a StackMob method for asynchronously saving the MOC found in the NSManagedObjectContext(Concurrency) Category Reference.
The view before this one performs a fetch on recent posts, and in the case where the fetch is not performed posting works fine, but if a fetch was performed then in saving the MOC in order to upload the new post I receive the following output as an error message:
2013-09-11 17:08:09.284 imageTagging[1824:1843] -[__NSDictionaryI bytes]: unrecognized
selector sent to instance 0x1e3123d0
2013-09-11 17:08:09.291 imageTagging[1824:1843] *** Terminating app due to uncaught
exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI bytes]: unrecognized
selector sent to instance 0x1e3123d0'
*** First throw call stack:
(0x318cb3e7 0x395c6963 0x318cef31 0x318cd64d 0x31825208 0x321631cf 0x3216b991 0x15ea99
0x318c8757 0x15e109 0x15dabf 0x10d1c3 0x318d05b7 0x10cd4d 0x10c829 0x10923b 0x1076d9
0x3166c431 0x316c44d1 0x1685c3 0x316c7e5d 0x399e3b3b 0x399e167d 0x399e4613 0x399e47d9
0x39a087f1 0x39a08684)
libc++abi.dylib: terminate called throwing an exception
(lldb)
The data is still uploading to the StackMob server, and can be called upon when the app is run later -- but the app crashes in trying to save it. All of this is performed in the view controller. I've tried to enforce all MOC saves to be performed on the main thread, but the error still occurs. I've also tried dispatching a "save queue" and updating the UI after save completes. This method seemed to work for a bit, but then the errors came up again (may have just been a fluke). I also tried to do this with the synchronous save calls in the documentation
The same error occurs when trying to perform other saves as well (such as after creating a new user or when updating a user's information), and all come down to the same function call causing the problems. It may also be worthwhile to note that the error is always the same (specifically that a type __NSDictionaryI is trying to access its unrecognized selector bytes.
Here is the full method call with the input parameters filled out:
//save context
[[[[SMClient defaultClient] coreDataStore] contextForCurrentThread] saveOnSuccess:^{
NSLog(#"You created a new Post object!");
[[[[SMClient defaultClient] coreDataStore] contextForCurrentThread] refreshObject:newPost mergeChanges:YES];
NSLog(#"refreshed");
} onFailure:^(NSError *error) {
NSLog(#"There was an error! %#", error);
}];
UPDATE: I have narrowed down the problem to a mishandling of information returned from a fetch performed by the previous view controller. Specifically, it occurs after the results are fetched in trying to use the data to update.
As a result of this new insight, the question I am really facing is how to properly save a managed object in the context after a fetch. I believe StackMob takes care of creating the managed objects after the fetch (i.e. server query). I've tried creating a new object from the results array (each "obj" is an NSManagedObject) with:
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSManagedObject *newObj = obj;
}];
I've also tried referencing the fetched results by object id (each "obj" is an objectID) with:
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSManagedObject *newObj = [self.managedObjectContext objectWithID:obj];
}];
Any insight on how to properly do this would be greatly appreciated!
UPDATE 2: It looks like the error is actually occurring in trying to use and save geolocation data. In order to use the queried objects' geolocation data, it must be unarchived -- but to save it, it must be archived. I'm looking into how to do this now, and if I come across a good solution, I'll update again.
FINAL UPDATE: Got it figured out! It turns out that the issue I was having was that I was unarchiving the geolocation data to update the UI and do some calculations, and while I was archiving it again to be stored properly, I created an annotation on a map that referenced the unarchived data. As a result, the MOC maintained the data that could not be saved via the StackMob methods. By only saving the archived data, I can save as often as I'd like and just unarchive the geodata when it needs to be used. Problem solved!
Please feel free to comment if anyone comes across a similar problem and needs some insight or references!
I am just going to put my final update in here as an answer, since it explains how I solved the issue.
FINAL UPDATE: Got it figured out! It turns out that the issue I was having was that I was unarchiving the geolocation data to update the UI and do some calculations, and while I was archiving it again to be stored properly, I created an annotation on a map that referenced the unarchived data. As a result, the MOC maintained the data that could not be saved via the StackMob methods. By only saving the archived data, I can save as often as I'd like and just unarchive the geodata when it needs to be used. Problem solved!
Please feel free to comment if anyone comes across a similar problem and needs some insight or references!
Moral of the story, if you are running into issues similar to this, make sure you are not (even if you don't mean to be) storing references to the unarchived SMGeoPoint data in any of your managed objects. It is trying to store those that causes the problem.

Merging Changes between NSManagedObjectContexts (Multithreaded)

I have a problem/crash merging the data of different NSManagedObjectContexts (iOS 6.1, Xcode 4.6).
Most of the time the error that rises is the following:
CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. statement is still active with userInfo (null)
One time i got this error:
An observer of NSManagedObjectContextDidSaveNotification illegally threw an exception. Objects saved = { $OBJLIST } and exception = statement is still active with userInfo = (null)
Sadly there is no value in the stacktraces i got. They just show symbols that are CoreData internal (if any).
Our CoreData stack:
1 NSPersistentStoreLocator shared by all threads
1 unique MOC per thread ( created on first need )
All MOCs are saved in a Dictionary
An observer is added for the notification NSManagedObjectContextDidSaveNotification to update the MOCs when one is saving to the store. The defined selector is calling mergeChangesFromContextDidSaveNotification on every other thread/context except the one that did the save operation.
+ (void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext *ctx;
for ( NSNumber *threadId in [__managedObjectContexts keyEnumerator] ) {
ctx = [__managedObjectContexts objectForKey:threadId];
if ( notification.object != ctx ) {
[ctx mergeChangesFromContextDidSaveNotification:notification];
}
}
}
Steps to produce the error:
In a background thread CoreData data objects that arent needed anymore (unreferenced by other objects) are being deleted.
[[CDUtils managedObjectContext] deleteObject:obj];
[[CDUtils managedObjectContext] save:&error];
While this is happening the user can navigate throughout the application. Userinteraction (i.e. opening a tableview) can trigger executeFetch calls on the moc of the main thread.
Every thread uses the same NSPersistentStoreLocator but a different/unique MOC.
We tried different methods of locking with NSLocks and the lock on the NSPersistentStoreLocator for threadsafety. I.e. enclosing the mergeChanges Method and the save operation each by a lock/unlock or enclosing both methods in the same lock/unlock. Sadly we had no succes thus far.
[__storeCoordinator lock];
[__storeCoordinator unlock];
I'd be thankful for every piece of advice you can give me to approach a solution. Thank you for your time!
For the people interested. I managed to make things work with multiple threads / MOCs. I basicly solved the original problem / those errors i had by locking PSC and MOCs correctly. The next problem that arose was how to know if its save to mergeChanges on a context. I cant lock nor should mergeChanges on a MOC that has no running thread anymore. But how do i know if the thread is running or not? If i just check for NSThreads "isExecuting"-Method it might happen that the thread exits just after i checked the BOOL. Im trying an easier approach now where i just merge into the main thread.

Core Data swallowing uncaught exception

I have run into a disturbing/baffling bug. Core Data seemed to be swallowing its own exception! When using the (super-useful) CoreDataHelper, I had written a badly formed fetch that resulted in a "Unimplemented SQL generation for predicate" exception. That part's simple, what's really weird is that this exception was being caught somewhere and swallowed, meaning that my code just skipped the rest of the method after that fetch and returned to the main loop without any console messages. Quite infuriating.
Eventually I was able to wrap the actual fetch request in a #try statement and #catch the exception:
#try{
fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"fetch successful");
}
#catch (NSException* exception) {
NSLog(#"caught exception!\n\n%#\n\n%#\n\n%#",[exception name], [exception reason], [exception userInfo]);
}
This let me figure out what it was, but it still makes no sense that it would be getting caught somewhere. I have not used #try/#catch anywhere else in my code other than to test this.
I also tried creating a blank Core Data project and using CoreDataHelper without the #try/#catch statement to try and isolate the problem, with nothing else in the project, CoreDataHelper works as it should, returning:
CoreDataTest[1044:11603] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unimplemented SQL generation for predicate ("test" LIKE attribute)'
So something in my project is catching and ignoring this exception, but it's not me unless I can somehow do that without using #catch.
What could it be?!
Got it!
I'm using an external accessory (Linea Pro-4 barcode scanner/MSR) that has its own library, and that is what is catching the exception, even in their newest version of the framework.
I set up my blank test project to connect to the accessory before running the same fetch request, and bam! Swallows the exception!

Resources