I'm building my first iOS app, which in theory should be pretty straightforward but I'm having difficulty making it sufficiently bulletproof for me to feel confident submitting it to the App Store.
Briefly, the main screen has a table view, upon selecting a row it segues to another table view that displays information relevant for the selected row in a master-detail fashion. The underlying data is retrieved as JSON data from a web service once a day and then cached in a Core Data store. The data previous to that day is deleted to stop the SQLite database file from growing indefinitely. All data persistence operations are performed using Core Data, with an NSFetchedResultsController underpinning the detail table view.
The problem I am seeing is that if you switch quickly between the master and detail screens several times whilst fresh data is being retrieved, parsed and saved, the app freezes or crashes completely. There seems to be some sort of race condition, maybe due to Core Data importing data in the background whilst the main thread is trying to perform a fetch, but I'm speculating. I've had trouble capturing any meaningful crash information, usually it's a SIGSEGV deep in the Core Data stack.
The table below shows the actual order of events that happen when the detail table view controller is loaded:
Main Thread Background Thread
viewDidLoad
Get JSON data (using AFNetworking)
Create child NSManagedObjectContext (MOC)
Parse JSON data
Insert managed objects in child MOC
Save child MOC
Post import completion notification
Receive import completion notification
Save parent MOC
Perform fetch and reload table view
Delete old managed objects in child MOC
Save child MOC
Post deletion completion notification
Receive deletion completion notification
Save parent MOC
Once the AFNetworking completion block is triggered when the JSON data has arrived, a nested NSManagedObjectContext is created and passed to an "importer" object that parses the JSON data and saves the objects to the Core Data store. The importer executes using the new performBlock method introduced in iOS 5:
NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[child setParentContext:self.managedObjectContext];
[child performBlock:^{
// Create importer instance, passing it the child MOC...
}];
The importer object observes its own MOC's NSManagedObjectContextDidSaveNotification and then posts its own notification which is observed by the detail table view controller. When this notification is posted the table view controller performs a save on its own (parent) MOC.
I use the same basic pattern with a "deleter" object for deleting the old data after the new data for the day has been imported. This occurs asynchronously after the new data has been fetched by the fetched results controller and the detail table view has been reloaded.
One thing I am not doing is observing any merge notifications or locking any of the managed object contexts or the persistent store coordinator. Is this something I should be doing? I'm a bit unsure how to architect this all correctly so would appreciate any advice.
Pre-iOS 5, we've usually had two NSManagedObjectContexts: one for the main thread, one for a background thread. The background thread can load or delete data and then save. The resulting NSManagedObjectContextDidSaveNotification was then passed (as you're doing) to the main thread. We called mergeChangesFromManagedObjectContextDidSaveNotification: to bring those in to the main thread context. This has worked well for us.
One important aspect of this is that the save: on the background thread blocks until after the mergeChangesFromManagedObjectContextDidSaveNotification: finishes running on the main thread (because we call mergeChanges... from the listener to that notification). This ensures that the main thread managed object context sees those changes. I don't know if you need to do this if you have a parent-child relationship, but you did in the old model to avoid various kinds of trouble.
I'm not sure what the advantage of having a parent-child relationship between the two contexts is. It seems from your description that the final save to disk happens on the main thread, which probably isn't ideal for performance reasons. (Especially if you might be deleting a large amount of data; the major cost for deletion in our apps has always happened during final save to disk.)
What code are you running when the controllers appear/disappear that could be causing core data trouble? What kinds of stack traces are you seeing the crash on?
Just an architectural idea:
With your stated data refresh pattern (once a day, FULL cycle of data deleted and added), I would actually be motivated to create a new persistent store each day (i.e. named for the calendar date), and then in the completion notification, have the table view setup a new fetchedresultscontroller associated with the new store (and likely a new MOC), and refresh using that. Then the app can (elsewhere, perhaps also triggered by that notification) completely destroy the "old" data store. This technique decouples the update processing from the data store that the app is currently using, and the "switch" to the new data might be considered dramatically more atomic, since the change happens simply be starting to point to the new data instead of hoping you aren't catching the store in an inconsistent state while new data is being written (but is not yet complete).
Obviously I have left some details out, but I tend to think that much data being changed while being used should be re-architected to reduce the likelihood of the kind of crash you are experiencing.
Happy to discuss further...
NSFetchedResultsController has been proven to be a bit sensitive to massive deletes so that is where I would start digging first.
My initial question is, how are the re-fetch and reload of tableview related to the start of delete operation. Is there a chance that the deletion block will save child MOC while the NSFetchedResultsController is still fetching or no?
Is it possible that when you switch from detail view to master and then back to detail view there will be multiple concurrent background tasks running? Or are you retrieving all the data from the web service at once not only that relevant to a particular row?
One alternative to make this more robust is to use a pattern similar to what UIManagedDocument uses:
Instead of using a parent MOC as Main Thread concurrency type, UIManagedDocument actually creates the main MOC as private queue and makes the child MOC available to you use on the main thread. The benefit here is that all the I/O goes on in the background and saves to the parent MOC does not interfere with the child MOC at all until child MOC is explicitly made know about them. That's because save commits changes from child to parent and not the other way around.
So if you did your deletes on a parent queue which is private, that would not end up in the NSFetchedResultsController scope at all. And since it is old data, that is actually the preferred way.
An alternative I offer is to use three contexts:
Main MOC (NSPrivateQueueConcurrencyType)
Responsible for persistent store and deletion of old data.
Child MOC A (NSMainQueueConcurrencyType)
Responsible for anything UI related and NSFetchedResultsController
Child MOC B (NSPrivateQueueConcurrencyType, child of Child MOC A)
Responsible for inserting new data and committing it up to Child MOC
A when done.
The main issue I've had with multi-threaded core data is inadvertently accessing a managed object in a thread/queue other than the one it was created in.
I've found a good debugging tool is add NSAsserts to check that to managed objects created in your main managed object context are only used there, and ones created in a background context aren't used in the main context.
This will involve subclassing NSManagedObjectContext and NSManagedObject:
Add a iVar to the MOC subclass and assign to it the queue it was created on.
Your MO subclass should check the current queue is the same as its MOC's queue property.
It's only a few lines of code, but long term can prevent you making errors that are hard to track down otherwise.
Related
[EDIT: simplified version of the question]
mainMOC is the primary managed object context
editorMOC is a managed object context created in editorViewController with an undo manager so the user can edit a single managed object
after editorMOC saves, mainMOC refreshes the updated managed object in the notification handler for NSManagedObjectContextDidSaveNotification
In the save handler, if I use [mainMOC refreshObject:obj mergeChanges:YES] the updates to the object are not reflected in mainMOC post-refresh. If I use [mainMOC refreshObject:obj mergeChanges:NO] the object is invalidated and at the next fault the changes are reflected in the data loaded from the store.
QUESTION: Why would the object not reflect the update when mergeChanges:YES is specified?
[ORIGINAL QUESTION]
I have a core data based app with multiple managed object contexts. The app is complicated and proprietary so I cannot simply share code directly from the app. I have created a simple test app in an attempt to reproduce my issue but the test app doesn't exhibit the problem. I have not been able to find a logical difference between the implementations. I apologize for not posting sample code, I believe I explained the implementation well below. If something is not clear, please ask in the comments and I will do my best to clarify.
Here's my situation. Everything described below is running on the main thread.
The app has a primary managed object context called mainMOC that is accessed on the main thread and used with NSFetchedResultsControllers to display data in various table views.
I have a view controller called EditorViewController that allows editing of an existing object of a particular entity. This view controller creates it's own managed object context called editorMOC using the same persistent store coordinator with an undo manager so changes can be rolled back or saved when dismissing the editor.
EditorViewController is observing the NSManagedObjectContextDidSaveNotification. When this notification occurs, the notification handler calls [_mainMOC mergeChangesFromContextDidSaveNotification:notification] to merge the changes from editorMOC into mainMOC.
The table view controller that uses an NSFetchedResultsController is handling the controller delegate messages.
I have added NSLog output to look at look at what happens in all of the above steps and I have verified the following:
I can see that the object is modified and saved in the editor.
I can see that the NSManagedObjectContextDidSaveNotification is called and that the updated object is included.
I can see that the fetched results controller is receiving the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: protocol message.
I can see that mainMOC is not reflecting the updates and that the updated object has not been invalidated by mergeChangesFromContextDidSaveNotification:.
If I quit and relaunch the app, the updates were committed
For reference, both my main app and test app implement the above functionality but the test app shows the updates merged correctly and the main app does not.
I am looking for suggestions on what would cause mergeChangesFromContextDidSaveNotification: not to successfully merge and/or invalidate the updated object.
Thanks!
The fetched results controller does not receive the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: delegate message. Instead it sends it to the delegate in response to a change in the context. If that delegate message is being sent and does contain the proper object, then the main context is aware of the changes.
If so, then your error is most likely in the the code interfacing the fetched results controller and the tableview. Something is preventing the object from appearing correctly.
The issue I had that caused mergeChangesFromContextDidSaveNotification not to work was I created a new NSPersistentStoreCoordinator for each new NSManagedObjectContext. When I shared NSPersistentStoreCoordinator between all MOCs, mergeChangesFromContextDidSaveNotification works perfectly. Also, make sure mergeChangesFromContextDidSaveNotification is called on the thread that owns the MOC.
From the research I did, it is safe to share NSPersistentStoreCoordinator between threads for use with NSManagedObjectContext. NSManagedObjectContext will lock the persistent store as necessary.
One possible answer: you have a third MOC you didn't know was there / forgot about. (This happened to me.)
I had
mainMOC
editorMOC
viewerMOC which came about by accidentally misguided subclassing - it was supposed to be looking at the main MOC, but instead was creating its own and looking at a frozen state. The "checking for edits" relationship was going the other direction, because it was expected to be the "editor" in this scenario.
the notification is correctly invoking the callback, and the data is being merged correctly into the main MOC (which i could tell because the data was correct on relaunch)
note: refreshObject:mergeChanges: is not needed. it's something i tried, too, when it wasn't working, but the merge of the notification should take care of all the objects for you.
- (void)twinStackUpdated:(NSNotification *)notification {
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
not sure that this was your problem, but it might be someones, so there it is.
In an effort to accept something even though I don't have a definitive solution, here's what I've done to address this and the problem seems to be resolved.
I believe this was a cache issue with NSFetchedResultsController. I've since simplified this code and made sure that each NSFetchedResultsController uses it's own cache. I've been able to remove the call to 'refreshObject' and things seem to be working correctly.
I have a UITableView that fetches data from CoreData using FetchedResultsController and it registers for the data update.
On a second thread, I download the data from the server and update the same data (that's used by the UITableView). The update is not complicated and It is just updating a BOOL field of the entity.
When I call the save on Object Context, I get this exception: NSInternalInconsistencyException and the reason is
"Failed to process pending changes before save. The context is still dirty after 100 attempts. ..."
If I do not save right after the update but only at the time when the application is about to terminate, the application runs fine and the UITableView is correctly updated and the data is persisted.
Any pointer on why that might be happening? Am I doing something wrong?
Managed object contexts are not thread safe. Do you have a separate MOC for each thread?
If so, I believe the correct pattern is to register for NSManagedObjectDidSaveNotifications from the background MOC such that you can do a mergeChangesFromContextDidSaveNotification on the main MOC (from the main thread). This will keep your MOCs in sync; it does not happen automatically.
I'm building an app with two tabs. The first tab has a main tableview that connects to a detail view of the row. The second tab will display a tableView based on the user adding content to it by tapping a button on the detail view.
My question is this. What is the correct design pattern to do this? Do I create a second ManagedObjectContext/ManagedObjectContextID then save that context to a new persistent store or can the MOC be saved to the existing store without affecting the original tableview?
I've looked at CoreData Recipes and CoreData Books and neither deal with multiple stores although books does deal with multiple MOC's. Any reference would be great.
A single NSManagedObjectContext is more than sufficient for this design. What you want to do is implement the dependency injection design pattern. What this translates to is that when you create each of your tabs, you pass the singular NSManagedObjectContext instance into each of them. Each NSViewController is then responsible for accessing the NSManagedObjectContext as needed.
update
If you are seeing the same data then that is an issue with your 'NSFetchedResultsController' and not the 'NSManagedObjectContext'. The 'NSManagedObjectContext' has access to all of the data, the 'NSFetchedResultsController' is what does all of the filtering based on it's 'NSFetchRequest'.
Perhaps you should post the 'NSFetchedResultsController' for each of your controllers (by editing your question here) so that we can see what is going on.
update
The MOC does not get destroyed, at all, ever. You simply have more than one reference/pointer to the same MOC. The MOC is the scratchpad for the NSManagedObject instances that you are accessing. When you call -save: on that MOC, it takes the changes in that scratchpad and persists them out to disk.
Except in some very, highly unusual, situations, you only ever need one MOC per thread. In your design that you have described so far, one MOC is more than sufficient.
Core Data newbie speaking here.
In my application I have two NSManagedObjectContext that refer to that same NSPersistentStorageController.
One ManagedObjectContext (c1) is in the main thread --created when I create a NSFetchedResultsController -- and the second ManagedObjectContext (c2) created in a second thread, running in the background, detached from the main thread.
In the background thread I pull some data off a website and insert the entities created for the pulled data in the thread's ManagedObjectContext (c2).
In the meanwhile, the main thread sits doing nothing and displaying a UITableView whose data do be display should be provided by the NSFetchedResultsController.
When the background thread has finished pulling the data and inserting entities in c2, c2 saves, and the background thread notifies the main thread that the processing has finished before it exiting.
As a matter of fact, the entities that I have inserted in c2 are know by c1 because it can ask it about one particular entity with [c1 existingObjectWithID:ObjectID error:&error];
I would expect at this point, if I call on my tableview reloadData to see some rows showing up with the data I pulled from the web in the background thread thanks to the NSFetchedResults controller which should react to the modifications of its ManagedObjectContext (c1).
But nothing is happening! Only if I restart the application I see what I have previously pulled from the web!
Where am I doing things wrong?
Thank you in advance!
Reloading the table will not refetch items from the persistent store as you are expecting. Reloading the table is a "UI" level action and fetching data is at the model level. While this is fine for any changes that occur in the main NSManagedObjectContext, this will not cause the main NSManagedObjectContext to become aware of changes that occurred in another NSManagedObjectContext.
Depending on your design you need to do one of the following:
When the main thread receives the notification on the background thread's save it needs to call -mergeChangesFromContextDidSaveNotification:.
When this is complete you need to save the main NSManagedObjectContext
During the save it will trigger your NSFetchedResultsController with changes
The NSFetchedResultsController will notify its delegate (normally your view controller) of the changes and give you an opportunity to update the table view.
I suspect that you are not doing one of those items and thus breaking the chain of events.
Example
There is a solid example of multi-thread notifications in my article over on the MDN. If you are not familiar with the MDN, I highly suggest taking a look.
I am trying to figure out the proper way to use NSManagedObjectContexts when you are viewing, editing, and creating NSManagedObjects. I feel that the documentation and examples have explained how to use them in the most basic cases, but I'm not sure what the proper methods are in a slightly more complex setup.
The setup:
3 main screens: a list of objects, an edit object screen, and a new object screen.
Another thread is downloading objects to add to the list in the background.
The requirements:
The list screen uses a MOC and an NSFetchedResultsController to get all it's objects.
The edit and new object screens use MOCs to save/delete objects AND use NSFetchedResultsControllers for relationships.
The downloaded objects need a MOC to save it's objects into Core Data (on the main thread).
The questions:
How many MOCs do I need?
How should I manipulate these MOCs?
Possible Answers:
Have one "View" MOC that is never edited and is used in the list screen. Use separate MOCs for the edit, new object screens, and the downloads. When these MOCs save, merge the changes back to the "View" MOC. This way any changes don't affect the "View" MOC until they are saved. This is what I have been doing; it doesn't seem to be working as smoothly as I hoped. There is a disconnect between the editing and the viewing and instead of being able to check on things when I know they might of changed I have to wait for NSFetchedResultsController delegate methods to finish and check on every possible thing that might have changed. It also makes it difficult if I need to change some data in the list view.
Have one MOC for everything. This is what I first tried but wasn't sure of how to deal with the editing and creating. Now that I understand a bit more, I guess I could just edit the object or create an object and rollBack on cancel. On cimgf, I saw a post that seemed similar which said to create an undoGrouping around the edit/create and undo on cancel. Then I guess I could use a separate MOC on the downloaded objects because it might finish and save while the user is editing in the main MOC.
Anyways, the point is that I don't know what the proper method is. Can you help me?
Example Disconnect for First Possible Answer
Created an object (1) in the edit moc. Saved. Merged with view moc by notification.
Created a new moc because I am downloading objects in the background. Updated some objects that are related to (1). Saved. Merged with view moc by notification.
Edit (1) in the edit moc. Saved. Merged with view moc by notification.
PROBLEM: Because the edit moc never got the new moc changes, when it saves, it deletes all the new moc changes affected by it.
SOLUTION: I realize that I could also merge the changes to the edit moc or always use a new moc to edit things. However, I keep running into little things like this and having to find solutions, so it leaves me to believe that this is not the best answer.
You should have at least one MOC per thread (they are not thread safe). So you could have a MOC for the downloader (in the background thread) and another in for the activity in the main thread list, edit and new.
When you say there is a disconnect, can you be more specific? Are you using notifications (NSManagedObjectContextDidSaveNotification) and doing mergeChangesFromContextDidSaveNotification when you receive that notification. Remember, that mergeChangesFromContextDidSaveNotification should be performed on the main thread.
In your view controller with NSFectchedResultsController are you handling all the cases of the NSFetchedResultsControllerDelegate correctly?