I’m building an app that pulls data from a remote server and stores them on a CoreData SQLite database. I’m fetching data from a background thread while the main thread consumes it. Here are the main approaches that I’m using.
- All the background threads has its own
NSManagedObjectContext. - The
persistentStoreCoordinatoris shared between the contexts. - On every fetch on the background thread, I save: the inserted objects and reset: the local context.
- Using a notification I make the merge with the main thread context.
The problems I’m experiencing:
- Randomly I’m getting crashes with no messages (
NSZombieand Crash Breakpoints are set) on the background threads save: operations. - There are lots of duplicated data being generated. The webservice data are for sure ok, so no problem on the server side.
Here goes the code.
The AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Update data from remote server
WebserviceDataModel *webservice = [[WebserviceDataModel alloc] init];
webservice.managedObjectContext = self.managedObjectContext;
[webservice startImport];
}
The background thread fetching and saving data on WebserviceDataModel
- (void)startImport
{
dispatch_queue_t downloadQueue = dispatch_queue_create("startImport in WebserviceDataModel", NULL);
dispatch_async(downloadQueue, ^{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
moc.persistentStoreCoordinator = self.managedObjectContext.persistentStoreCoordinator;
// get the remote data
NSDictionary *lojas = [Loja allLojasFromRemoteServer];
for (NSDictionary *lojaInfo in lojas) {
Loja *loja __attribute__((unused)) = [Loja lojaWithRemoteData:lojaInfo inManagedObjectContext:moc];
}
if ([moc hasChanges]) {
[moc save:nil];
[moc reset];
}
[moc release];
});
dispatch_release(downloadQueue);
}
The NSManagedObject method for object creation: + (Loja *)lojaWithRemoteData:inManagedContext:
+ (Loja *)lojaWithRemoteData:(NSDictionary *)remoteData inManagedObjectContext:(NSManagedObjectContext *)context
{
Loja *loja = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Loja" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:@"lojaId = %d", [[remoteData objectForKey:@"lojaId"] intValue]];
NSError *error = nil;
loja = [[context executeFetchRequest:request error:&error] lastObject];
[request release];
if (!error && !loja) {
// create the record
loja = [NSEntityDescription insertNewObjectForEntityForName:@"Loja" inManagedObjectContext:context];
loja.lojaId = [remoteData objectForKey:@"lojaId"];
loja.email = [remoteData objectForKey:@"email"];
loja.facebook = [remoteData objectForKey:@"facebook"];
// ... and others...
}
return loja;
}
Subscription to the NSManagedObjectContextDidSaveNotification on WebserviceDataModel
- (id)init
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
}
return self;
}
The contextChanged: method on WebserviceDataModel
- (void)contextChanged:(NSNotification*)notification
{
if ([notification object] == self.managedObjectContext) return;
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
return;
}
[[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
Well, I had figured it out. And its working like a charm now. The implementation of multithreaded contexts is correct. The problem was with the
applicationDidBecomeActive:My app uses a CoreLocation Fence for a list of features. When the app launches for the first time, the CoreLocation framework show an alert message for the user saying that the app uses de user location… That alert callsapplicationDidBecomeActive:again, creating two concurrent waves of updates. Just moved my WebserviceDataModel to a property and implemented a flag to know if its running or not. Thats itJust for a final refinement, changed the merge policy to
NSMergeByPropertyStoreTrumpMergePolicy, so now the server side data (in memory) wins over the local store.