views:

89

answers:

6

I'm running into a crash issue while developing an iPhone app with Core Data.

The app syncs data with a webservice on a background thread.

When the app first launches, the existing data in the Core Data DB will be displayed to the user in a UITableView, while a background thread is kicked off the grab the latest data from the web service API. Lets call the core data models User and Items, just for discussion purposes with a User having many items. The items are what is displayed in the UITableView.

When the API results come back, the users existing Item records are deleted from Core data and the new set of Item records is inserted into Core Data. Once all items are parsed, a message is sent back to the main thread to merge in the core data changes and refetch the data from CD.

However, I keep crashing in my configure table cell routine, and I'm sure its because of out of scope objects. Meaning, the main thread is trying to display objects from Core Data, while the background thread is deleting those same objects from Core data and replacing them with new ones.

What is the best way to handle these kind of conflicts? Do I have to put in some kind of mechanism to not kick off the background thread updating until the main view has loaded/displayed all its data? If so, how do I accomplish this? Can I still keep my same methodology and just handle the deletion of displayed items better?

A: 

Are those Core Data operations so expensive that you can't do this from the main thread? Just do the webservice part on another thread. Otherwise, it probably will get more difficult - maybe put some controller class in between that caches the relevant data or synchronizes access to Core Data.

Eiko
the expensive part is just the potential latency in getting the data to and from the API server. dont want to block the UI while uploading or, in this case, downloading updated data from the API server.The core data operations themselves aren't major, no. I guess I could refactor my work around and perform the actual CD delete/insert methods on the main thread if that is the optimal solution
cpjolicoeur
are there any native caching solutions or would I have to roll my own Cache controller?
cpjolicoeur
`NSFetchedResultsController` is made to interface Core Data to a `UITableView` in a safe manner, which also handles changes to the data as you make them.
paxswill
A: 

How are you accessing your data for the table? If you're using an NSFetchedResultsController, it should be automatically updating the cells in a safe manner.

In the scope of your original question:
Unless your data is saved somewhere else, redrawing the cells as you scroll through the table accesses the data. You could copy all of the data you're using to populate the cells, but that would spike your memory usage.

paxswill
I'm using an NSFetchedResultsController to display the data in the UITableView. I thought the frc managed the handling for you, but my guess is that its trying to display data at the same time its being deleted in the background thread, and doesnt know its deleted yet because of the seperate managedObjectContexts and that it hasnt been told to merge in the new changes yet
cpjolicoeur
A: 

Are the main thread and the background thread each using their own Core Data managed object context? You also might want to review the Core Data multithreading tips at

http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/CoreData/Articles/cdMultiThreading.html

David Gelhar
no, the two threads are using seperate managed object contexts but a single persistent store coordinator. I'm reviewing those docs as we speak, btw.
cpjolicoeur
A: 

Check your NSFetchedResultsControllerDelegate methods controllerWillChangeContent: and controllerDidChangeContent:.

You are responsible for notifying the table that you will be altering it with the beginUpdates and endUpdates messages. When the fetched results controller is going to modified the returned data, the table needs to freeze updating itself until the controller is done. Otherwise, the table is trying to represent a constantly shifting list of data.

TechZen
I'm a little confused I think. Those methods you mentioned are part of the FRC delegate protocol, correct? They aren't something I currently call manually. How would I call them manually from my background thread? Or is that not what you are implying to do?
cpjolicoeur
you implement the delegate methods. The messages are sent to the object that is the delegate of your FRC instance. This is typically the same controller object that is managing the tableView.
falconcreek
right. I do have those delegate methods implemented in the view controller that holds the FRC that is managing the UITableView. What I don't follow is what the has to do with my background thread controller that is doing the delete and updates on the background thread? How does that effect and/or call the FRC delegate methods on the view controller on the main thread to prevent the deletion crash issue?
cpjolicoeur
they are not being called because your FRC doesn't know that changes have taken place because you haven't registered for the save notification.
falconcreek
see my comment on your other post. I do register for the notification in the view controller that contains my FRC
cpjolicoeur
A: 

Refer to the TopSongs project SongsViewController implementation Specifically viewDidLoad and handleSaveNotification

- (void)handleSaveNotification:(NSNotification *)aNotification {
[managedObjectContext mergeChangesFromContextDidSaveNotification:aNotification];
[self fetch];
}

Your viewController's handleSaveNotification: is not being called because you registered for notifications from the MOC that it owns, not all MOC's. It works for the the TopSongs sample as the app delegate and viewController's MOC are the same object. (appDelegate passes the moc to songsViewController).

Replace:

// ViewController.m viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];

With:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];

Replace:

// ViewController.m viewDidUnload
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.managedObjectContext];

With:

[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:nil];
falconcreek
this actually is exactly what I'm doing. I modeled most of my background updating code of the TopSongs example project from Apple.
cpjolicoeur
it's fine to register for the same notification in two places, correct? I register for this notification in the view controller and in my app delegate, just like the example in the TopSongs projectif I put log messages in both callbacks though, I only see the log messages from the one in my app delegate, not the one in my view controller
cpjolicoeur
yes. the beauty of notifications.
falconcreek
ok, thats what I thought from reading the docs. Not sure why both methods aren't receiving the same notification callback though even though they both register for it. Only the App delegate callback is being run.
cpjolicoeur
the difference with the TopSongs demo is that they just blow away the persistent store completely and create a new one before doing the sync. I can't do this as I have multiple users so I can only delete Item objects for the specific user, not for everyone. Plus I have other tables that can't be just blown away.
cpjolicoeur
you will have to post code showing how you register for the notification and the `action` methods to determine why your notifications are not firing your viewController `handleSaveNotification:`. what is your app delegate doing when it receives the notification? i am not suggesting that you start with a new store. just refresh the entities that you need to.
falconcreek
sure, here you go:http://gist.github.com/456122This shows the view controller registering for the callback and the callback method, as well as the app delegate callback method and the importer registering the app delegate for the callback
cpjolicoeur
updated answer based on code provided
falconcreek
I dont see what you are saying about the TopSongs example. The TopSongs example sets the MOC for the songsViewController with the same MOC as the app delegate. I do the same thing for my viewController by calling a custom initWithManagedObjectContext method. However, the TopSongs example uses a seperate MOC for the iTunesRSSImporter controller, not the same MOC as the appDelegate/songsViewController. That is the same thing I'm doing.I'm going to try your code, but could you expound a bit more on that please as you indicated TopSongs is all using the same MOC throughout?
cpjolicoeur
can you add your `fetch` and `confgureCell` methods? it appears that you are doing the right thing. (apologies for in invalid statement about the MOC being shared, not what I intended to say. answer has been corrected) Another thing that you do have to check for is the merge policy set for your insertionContext. Since you are not setting up a new store, this is an extra step that you need to do. When are you performing the save operation? Are you sure that the save is completing successfully?
falconcreek
http://gist.github.com/456224Ok, here is a gist with my `fetch` and `configureCell` methods from the ViewController as well as the `insertionContext` from the importer. I'm not doing anything with merge policies, so can you explain that a bit more?I'm doing the save at the end of creating all the new objects in the importer like so NSError *saveError = nil; NSAssert1([insertionContext save:The save is working because they are there the next app load
cpjolicoeur
also, are you saying I DO need to create a seperate persistent store coordinator to handle the insertions/deletions in concurrent operation?
cpjolicoeur
you should not need a separate PSC.not sure why your VC's FRC is not getting the merge message. can you set a breakpoint on appDelegate's `wishlistImporterDidSave:` method?. i am assuming that you are using `@property(nonatomic, retain)` when the declaring your VC and AppDelegate properties? you can read about the merge policy http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObjectContext_Class/NSManagedObjectContext.html#//apple_ref/doc/uid/TP30001182-SW8
falconcreek
yes, the appDelegate and VC's properties are all set to nonatomic, retain with a few also being set to readonly in the appDelegate (operationQueue, managedObjectContext, persistentStoreCoordinator)I'll set a breakpoint on that method, but what exactly should I be checking for? Care to chat on IM or email instead of via StackOverflow? If so, I'll just accept this answer and close it out
cpjolicoeur
It's bad form to take a conversation offsite. If you do need to do so, you should at least perform the courtesy of posting the eventual resolution. Part of Stackoverflow's function is to serve as as repository for answered questions.
TechZen
I definitely will post the solution once it is determined. The issue is still being worked out, but once it's fixed I'll update the thread here. It just seemed like myself and falconcreek were chatting back and forth via the limited comments section, so we took it offline.
cpjolicoeur
A: 

Have you resolved this ?

Check that you a) Are not using caching - i.e your call when you initially create the FRC does not use caching:

 NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil]

(See cacheName:nil)

b) Are you using a Predicate in the FRC ? I'm having all sorts of problems with this now, trying to do the same thing as your code.

ferdil
No, I haven't found a solution yet and yes, I am using caching actually. I'll try it without caching to see if it fixes my problem. Why would caching cause the error described?Also, I am using a predicate in my FRC, yes.
cpjolicoeur