Core Data crash (EXC_BAD_ACCESS) on synchronizing contexts in mergeChangesFromContextDidSaveNotification - ios

I'm writing an iPad cookery application which is heavily based on CoreData.
In my app I have a screen for editing recipe with 2 types of information:
Lists with recipe category\subcategory and country
All other recipe-related information
Each of this list may be edited in popover. Changes of this list shall be persisted immediately (i.e. if user will add some recipe category to possible categories list, but cancels recipe creation, this category shall be available for all recipes).
It was descided to implement 2 separate NSManagedObjectContexts to handle each type of info: main context for recipes management and supporting context for lists.
All core data operations performed via MagicalRecord framework. Both of contexts having MagicalRecord's default context as parent. Each context observes changes in other one. All contexts are being created and used on main thread so it seems that this issue has nothing related to multithreading issues.
When I'm trying to create object in support context and persist changes in support context immediately after object's creation everything going OK. The problem comes when newly created object is being deleted right after creation - EXC_BAD_ACCES received.
However, the entity is being persisted correctly and on next launch it may be used and deleted without synchronization issues.
1 note: When object is being accessed from Main context by existingObjectWithID: method of NSManagedObjectContext it becomes possible to delete this object. However crash happens on main context's (parent context of both Main and Supporting contexts) saving then.
Here is my code:
Entity creation:
RecipeCategory* category = [RecipeCategory MR_createInContext:_supportingContext];
category.name = itemName;
[_supportingContext MR_saveToPersistentStoreAndWait];
Entity deletion:
[(RecipeCategory*)itemToRemove MR_deleteEntity];
[_supportingContext MR_saveToPersistentStoreAndWait];
Contexts creation and observing setup:
[_mainContext MR_stopObservingContext:_supportingContext];
[_supportingContext MR_stopObservingContext:_mainContext];
_mainContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
_supportingContext = [NSManagedObjectContext MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
[_mainContext MR_observeContextOnMainThread:_supportingContext];
[_supportingContext MR_observeContextOnMainThread:_mainContext];
Please advice what may cause this issue because now I'm confused even in which way shall I move to solve this. Changes management section in Core Data documentation gives nothing. Same results gave google.

Don't do that. A context watching another that is also watching the watcher...that's bad. You first need to understand the rules of nested contexts and how data flows from one to the other on save.
I your case, you may want to look for the MR_confinementContext method on NSMOC. This will create a context that uses the old thread confinement model. This may help you get around your thread crashes. But first, don't do the circular observing...data flows in

Related

When to init the NSManagedObjectContext?

I have an iOS app with master and detail layout.
In master, I managed my own NSManagedObjectContext, and also detail is, by this way:
NSPersistentStoreCoordinator *psc = ((AppDelegate *)UIApplication.sharedApplication.delegate).persistentStoreCoordinator;
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setPersistentStoreCoordinator:psc];
In master, I display list that can be clicked by user to show the detail in detail layout.
Upon filling the detail by user, user can save the detail by clicking on button there.
However, I am trying to understand this:
Since there is a save button in detail, the save method will save the detail with detail's context and call the load list in master
Load list will remove all the NSMutableArray of the list [_activities removeAllObjects]; and re-fetch the data from Core Data and reload the tableview
Done that but the the re-fetch function seems to use old data and not the latest.
Why does re-fetch the data doesn't work if I use same context?
You are using outdated APIs to create your managed object contexts. After creating a context, instead of assigning the persistent store, you should set the parentContext.
If you display a "list", you should be using a context that is of type NSMainQueueConcurrencyType. The best way is a NSFetchedResultsController which will also help you manage memory and performance, and greatly simplify the updates you need for your UI. (You would avoid the verbosity and complexity of merging the data "manually" via NSManagedObjectContextDidSaveNotification.)
NB: Resetting the context is a crude and inefficient method for the task you are trying to accomplish. Due to the fact that it affects the entire context, it could change the behavior in other parts of your app.
You have two contexts going on here which is why the data is not being refreshed. If you call [context reset] then you will find that the data will refresh.
Explanation:
An NSManagedObjedctContext is a "scratchpad" which manages the Objective-C representation of SQLite (or XML) data in memory. When you fetch data the context retains the managed objects that are created by the fetch. If you then fetch from another context that context reads the data from the persistent store and creates new managed objects based on what it finds.
ALSO: When you perform a fetch the context checks to see if a managed object representation is ALREADY in existence and then returns it if it is. This means that two contexts can get out of sync quite quickly.
There are several ways around this:
1) Calling reset on a context returns it to it's "baseState" which means all the managed objects are "forgotten" or released by the context. Therefore, another fetch will force the context to read the data directly from the store.
2) Implementing the NSManagedObjectContextDidSaveNotification (see here) will allow you to incorporate changes between contexts.
I have found this article very useful in the past.

UIManagedDocument parent context object insertion on a background priority queue not updating in UI using child context

I'm trying to implement some basic UIManagedDocument import/export functionality into my app, mainly for dev so that I can easily inspect the document contents and more crucially preserve a set of test data when I start trying to iterate on my CoreData models.
All I am trying to do is load some JSON data from a local file and inject it into my apps UIManagedDocument. The UIManagedDocument's ManagedObjectContext contents are visualised in my app using some Core Data Table View Controllers from the Stanford iOS courses.
I thought I'd try to write this with some threading to keep the UI responsive and to learn how to do it. So I've done something like this
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext
} );
At first I thought this was working. No errors, asserts, crashes triggered and upon looking at my CoreData TableViews I could see the newly added objects in my UI. Unfortunately the newly added objects were seemingly never saved back to the store. I even hooked up to listen to the NSManagedObjectContextDidSaveNotification from my UIDocument's managedObjectContext and saw it wasn't triggering on pressing the home button, like it usually does if it has some changes performed in the app with my UI pending. Infact even doing these operations in the UI wouldn't cause the notification and saving to occur so it was clearly not happy.
I unrolled the code from within the background queue and ran it on the main thread synchronously and everything worked ok, the new data was saved correctly.
I started reading about the complexities of threading and coredata, the documentation seemed to suggest using the UIDocument's ManagedObjectContext's parent ManagedObjectContext to perform operations on in the background so I tried doing the same code again using this parent context, so as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
// try to create a new core data entity with my document's ManagedObjectContext parent ManagedObjectContext
} );
This time for some reason the CoreData TableView controllers no longer updated to show the newly injected objects. Even after explicitly calling save on the parent context, nothing appeared. However on quitting the app and reloading the app the newly injected objects did seem to be added correctly. Interestingly at this point i'd left a fetchrequest with a cachename specified and that threw up an error on this first run of the app after injecting the objects this way. I guess somehow the way the object had come from the parent context directly perhaps invalidated the cache somehow, that's still something I don't fully understand. Even changing the cache to nil didn't fix the issue of the table views not updated the same session as when the objects were injected into the parent context.
Looking elsewhere I've seen some uses of the managedObjectContext performBlock suggested. Another case where someone has said you must call
[document updateChangeCount:UIDocumentChangeDone]
after all changes to ensure the saving is performed, or perhaps using
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
instead. Though elsewhere I've seen mentioned that saving should be enough to push context contents through the hierarchy. Does saving only work from child -> parent and not from parent -> child.
Or am I just doing it wrong?
Anyone's time and help is really appreciated! Cheers.
please look at the 'Parent/Child Contexts' section in the Multi-Context CoreData.
Whenever a child MOC saves the parent learns about these changes and this causes the fetched results controllers to be informed about these changes as well. This does not yet persist the data however, since the background MOCs don’t know about the PSC. To get the data to disk you need an additional saveContext: on the main queue MOC.
I do saving to the PSC n following way:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
{
// read JSON data file
// parse JSON into dictionaries etc
// iterate over dictionaries
NSError* error;
if (self.managedObjectContext.hasChanges)
{
[self.managedObjectContext save: &error];
}
// Save main context
dispatch_sync(dispatch_get_main_queue(), ^
{
[[AXContextHelper sharedInstance] saveMainContext];
});
}
After looking into all the suggestions I could find for similar problems, none of them seemed to help.
The scenario i was describing in the end had the store stuff to file handled ok, but the UI not updating. At the time when I do this import/export I'm in a view ontop of the core data table view controller that doesn't update when I inject all these JSON objects, so in the end all I did was force that controller to re-fetch it's data. I believe the NSFetchedResultsController is meant to monitor the managedObjectContext and update the fetch request as required. For whatever reason, this wasn't working.
With the force re-fetch called, everything seems to work ok. The newly injected entities appear in my tables, and are saved to the store files.
Either there are some bugs in these objects, or I'm still using it wrong but I see other problems... my import/export data both work now on background threads with the parent context. What I've noticed today is that I can use the UI to properly insert and edit some objects using the child context on the main thread. I don't deliberately call anything at the moment after making these edits so i guess the edits are are still pending till core data decides to save. If i then go to my export feature and use it using the parent context then i find the newly edited or inserted objects aren't there.
Clearly there's no messaging happening in time from child -> parent, and messaging from background thread edits on the the parent itself just doesn't seem to work.. I never get any notifications for the edits i perform in a thread on the parent context, even though they seem to work. If I force the save to happen via the documents then the passed in dictionaries show no edits being made, even though the edits are saved correctly to store files somehow.
I guess i now need to force my document to save after every operation, or at least before every operation i'm about to do on another thread to ensure the parent/child are both in sync.
Still the problem I originally described I've managed to get around, but I'm still left wondering quite how I should be using the parent and child context of a UIManagedDocument to avoid encountering these issues.

iOS5 NSFetchedResultsController not getting delete updates

I have a NSFetchedResultsController tied to my main managed object context. It is in charge of keeping data for a table view in my main view.
I have an NSOperation running on a background thread that refreshes/deletes the managed objects that the fetched results controller is keeping track of. I create a child context (private concurrency type / parent = main managed object context) in the nsoperation and insert/delete objects. When it is finished, it saves its context, as well as the parent context.
What's interesting and very frustrating is that this works fine in iOS 6. When I insert or delete objects, my fetched results controller is notified of the changes and everything works as expected. However, on iOS 5, everything works except for deletes. The fetched results controller is not notified of a delete. However... if I manually refresh the fetched results controller (making it nil and refetching the same predicate) then it will show the expected result.
Also, when I register for change/save notifications on the main context, I can see that the user info dictionary contains the objects that I've deleted... even in iOS 5!
One issue that I thought it may be, but I don't think holds because I've removed the factors, is that this object is in a many to one relationship with another object. The object I am deleting/inserting is an "employee" and it has a relationship with a "department". The employee is set to nullify and the department is set to cascade.
As I said, this works fine in iOS6 but not in iOS5.
Any tips would be very helpful.
This bug is due to saving to the persistent store. This child context saves itself, then calls perform block on it's parent, the main managed object context. When the main managed object context saves, it triggers a background context to write to the persistent store. When I removed the background context save, the fetched results controller updated as expected.
Something interesting that I found that was probably causing this was that the managed object was leaking every time I tried saving to the store. Not exactly sure how to fix this yet, but it's good to know the reason for it.

iOS 6 What is the standard way to deal with background saving of managed objects (in 2013)?

Last year I used RestKit 0.10 to seamlessly download and save core data objects in background. However, when I tried to use restkit in 2013, I noticed that they have taken out the ActiveRecord pattern, which I relied upon to abstract away all the unpleasantness of background saving.
I found that the ActiveRecord pattern exists in MagicalRecord framework, but most of the documentation I could find is for version 2.x, while my cocoapods install 3.x.
I spent the last 2 hours searching, and find a lot of answers that are really out of date and no longer work for these new frameworks.
This poses the question: what the standard/easiest way to deal with saving core data objects in background using frameworks available in 2013? Should I try some other framework?
If you don't use any external library like Magical Record or RestKit, but simply go for the all manual stuff, you can take advantage of the new NSManagedObjectContext APIs.
You can now have contexts nested with a parent-child relationship and you can also tell each context to perform a block in it's own thread. My advice, therefore is to have the following structure for your application:
1) A background saving context. This will be the only context that saves and reads data directly to/from the database.
2) A context initalized on the main thread that will be your point of access for everything you need to do in the application, especially updating the UI. This context will be a child of the saving context.
3) As needed, you'll create background contextes that perform work on background threads, e.g. loading data from the network and serialize this data in NSManagedObject instances. This contextes will be children of the main context.
4) Every time you call -[NSManagedObjectContext save:] on a context, you should also call the same method on it's parentContext. To do this you could have a convenience method in a category on NSManagedObjectContext that reads something like this:
- (void)saveSelfAndParent {
[self save:NULL];
[self.parentContext performBlock:^{
[self.parentContext saveSelfAndParent];
}];
}
This is already a thread safe configuration and your changes will propagate the changes up to the database. Note that as the saving context will have no parent (and thus self.parentContext will be nil), the performBlock: won't crash the app.
Here's an example what you need to do to create a new entity assuming you kick off your background work using Grand Central Dispatch (GCD):
dispatch_async(dispatch_async_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType];
context.parentContext = mainContext;
// do some expensive job
...
// initialize a new NSManagedObject instance using the information we calculated
NSManagedObject *myObject = ...;
// once we're done, let's save the context
[context saveSelfAndParent];
});
Note that we initialized the context with a private queue concurrency type (NSPrivateQueueConcurrencyType) which tells the context that he's a background context. That is very important!
That's all! :)
For more information refer to the NSManagedObjectContext Class Reference.

Core Data multi thread application

I'm trying to use core data in a multi thread way.
I simply want to show the application with the previously downloaded data while downloading new data in background.
This should let the user access the application during update process.
I have a NSURLConnection which download the file asyncronously using delegate (and showing the progress), then i use an XMLParser to parse the new data and create new NSManagedObjects in a separate context, with its own persistentStore and using a separate thread.
The problem is that creating new objects in the same context of the old one while showing it can throws BAD_INSTRUCTION exception.
So, I decided to use a separate context for the new data, but I can't figure out a way to move all the objects to the other context once finished.
Paolo aka SlowTree
The Apple Concurrency with Core Data documentation is the place to start. Read it really carefully... I was bitten many times by my misunderstandings!
Basic rules are:
Use one NSPersistentStoreCoordinator per program. You don't need them per thread.
Create one NSManagedObjectContext per thread.
Never pass an NSManagedObject on a thread to the other thread.
Instead, get the object IDs via -objectID and pass it to the other thread.
More rules:
Make sure you save the object into the store before getting the object ID. Until saved, they're temporary, and you can't access them from another thread.
And beware of the merge policies if you make changes to the managed objects from more than one thread.
NSManagedObjectContext's -mergeChangesFromContextDidSaveNotification: is helpful.
But let me repeat, please read the document carefully! It's really worth it!
Currently [May 2015] the Apple Concurrency with Core Data documentation is, at best, very misleading as it doesn't cover any of the enhancements in iOS 5 and hence no longer shows the best ways to use core data concurrently. There are two very important changes in iOS 5 - parent contexts and new concurrency/threading types.
I have not yet found any written documentation that comprehensively covers these new features, but the WWDC 2012 video "Session 214 - Core Data Best Practices" does explain it all very well.
Magical Record uses these new features and may be worth a look.
The real basics are still the same - you can still only use managed objects the thread their managed object context was created on.
You can now use [moc performBlock:] to run code on the right thread.
There's no need to use mergeChangesFromContextDidSaveNotification: anymore; instead create a child context to make the changes, then save the child context. Saving the child context will automatically push the changes up into the parent context, and to save the changes to disk just perform a save on the parent context in it's thread.
For this to work you must create the parent context with a concurrent type, eg:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Then on the background thread:
context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[context setParentContext:mainManagedObjectContext];
<... perform actions on context ...>
NSError *error;
if (![context save:&error])
{
<... handle error ...>
}
[mainManagedObjectContext performBlock:^{
NSError *e = nil;
if (![mainContext save:&e])
{
<... handle error ...>
}
}];
I hope this can help all the peoples that meet problems using core data in a multithread environment.
Take a look at "Top Songs 2" in apple documentation. With this code i took the "red pill" of Matrix, and discovered a new world, without double free error, and without faults. :D
Hope this helps.
Paolo
p.s.
Many thanks to Yuji, in the documentation you described above I found that example.

Resources