I use coredata in my app, with 3 contexts:
__masterManagedObjectContext -> is the context that has the NSPersistentStoreCoordinator and save the data to disk.
_mainManagedObjectContext -> is the context used by app, everywhere
dispatchContext -> context used in background method, where i have my webservice access and all coredata insertion/update stuff.
I'll put some code to realize my solution:
App initialisation code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions //a app começa aqui
{
NSPersistentStoreCoordinator *coordinator = [self newPersistentStoreCoordinator];
__masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[__masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainManagedObjectContext setUndoManager:nil];
[_mainManagedObjectContext setParentContext:__masterManagedObjectContext];
return YES;
}
Method to create a new store cordinator
- (NSPersistentStoreCoordinator *)newPersistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self newManagedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return pC;
}
- (NSManagedObjectModel *)newManagedObjectModel
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return newManagedObjectModel;
}
Thread call with context specification (essential code):
@try
{
dispatchContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[dispatchContext setUndoManager:nil];
[dispatchContext setParentContext:__masterManagedObjectContext];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:dispatchContext];
if(dispatchContext != nil)
{
[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
}
else
{
NSLog(@"context IS NIL");
}
}
The background method:
- (void)parseDataWithObjects
{
[dispatchContext lock];
...
webservice data parse, and core data inserting/updating (+/- 5MB)
...
[dispatchContext save:&error];
[dispatchContext unlock];
[__masterManagedObjectContext save:nil];
}
This method is caled in all UI to access coredata data.
- (NSManagedObjectContext *)managedObjectContext
{
return _mainManagedObjectContext;
}
A calling example:
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
...fetching, update, ...
Now, my really problem:
Whenever the master context is to be saved ([__masterManagedObjectContext save:nil];
, on background), when I try to access the main context (_mainManagedObjectContext
), the app freezes (maybe a lock?).
The saving process takes a long time (because are a lots of data (aprox. 6mb)). While saving, the app turns slow and if i access some data while this process is running, my app freezes forever (i need to force quit).
Another problem is to merge the contexts. Imagine, using the main context in other viewController, and saving that context, everything works fine until i close de app. When i open the app again, nothing was saved.
What i'm doing wrong? Until now, this contexts are confusing to me. Someone can help me? I really appreciate :)
---------- EDIT:
Following Florian Kugler response, now i have only 2 context, every one with the same coordinator.
When my app is initialised, i call this method:
-(void) createContexts
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
NSError *error = nil;
pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainManagedObjectContext.persistentStoreCoordinator = pC;
}
- (void)mergeChanges:(NSNotification*)notification {
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}];
}
- (void)saveMasterContext
{
[mainManagedObjectContext performBlock:^{
[mainManagedObjectContext save:nil];
}];
}
To start my data import (on background), i use this code:
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
My background method:
- (void)parseDataWithObjects
{
[self resetTime];
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = pC;
...
[backgroundContext save:&error];
}
Resuming...
- 1st - I create my main context
- 2nd - I define background context notification, to merge the changes after save.
- 3rd - I call the background method
- 4rd - I save my background context
And the performance is really better. But the app freezes a little, i think is on "mergeChanges". I'm doing something wrong?
[NSThread detachNew...]
dance. You can just initialize the background context withNSPrivateQueueConcurrencyType
and then do `[backgroundContext performBlock:^{ ...your importing code... }]. Everything you do in this block will be performed on a background thread. To reduce the effect of merging you should save the background MOC regularly. Don't import everything and then save, but save every 100-200 objects. – Florian KuglerperformBlock:
call on the background context is all you have to do to get concurrency in this case. – Florian Kugler