Core Datawith multiple managed
object contexts
Photo by Free-Photo-Gallery.org
Core Datawith multiple managed
object contexts
Photo by Free-Photo-Gallery.org
Matt(hew) Morey
matthewmorey.com | @xzolian
Senior Developer at ChaiONETraveler
Boardsport Junkie
Agenda
1)Core Data Basics2)Concurrency Problems3)Concurrency Solutions
Agenda
1)Core Data Basics2)Concurrency Problems3)Concurrency Solutions
Basics
Basics
AppAppAppAppAppApp
NSManagedObjectNSManagedObjectNSManagedObjectNSManagedObjectNSManagedObjectNSManagedObject
NSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContext
NSPersistentStoreCoordinatorNSPersistentStoreCoordinatorNSPersistentStoreCoordinator NSManagedObjectModelNSManagedObjectModelNSManagedObjectModel
NSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStore
SQLite XML BinaryBinary In Memory Custom
Basics
AppAppAppAppAppApp
NSManagedObjectNSManagedObjectNSManagedObjectNSManagedObjectNSManagedObjectNSManagedObject
NSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContext
NSPersistentStoreCoordinatorNSPersistentStoreCoordinatorNSPersistentStoreCoordinator NSManagedObjectModelNSManagedObjectModelNSManagedObjectModel
NSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStore
SQLite XML BinaryBinary In Memory Custom
Managed Object Model
Managed Object Model
Managed Object Model- (NSManagedObjectModel *)managedObjectModel{ if (_managedObjectModel != nil) { return _managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"core-data" withExtension:@"momd"]; _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return _managedObjectModel;}
Persistent Store Coordinator
Persistent STore Coordinator- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{ if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"core-data.sqlite"]; NSError *error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { /* Replace this implementation with code to handle the error appropriately. ... */ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator;}
Persistent STore Coordinator- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{ if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"core-data.sqlite"]; NSError *error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { /* Replace this implementation with code to handle the error appropriately. ... */ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator;}
Managed Object Context
Managed Object Context- (NSManagedObjectContext *)managedObjectContext{ if (_managedObjectContext != nil) { return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) { _managedObjectContext = [[NSManagedObjectContext alloc] init]; [_managedObjectContext setPersistentStoreCoordinator:coordinator]; } return _managedObjectContext;}
Single Context
Single Context
Single Context
Code Examplehttps://github.com/mmorey/CoreDataMultiContext/tree/blocking
Agenda
1)Core Data Basics2)Concurrency Problems3)Concurrency Solutions
Problems
‣Core Data Managed Objects are not thread safe‣Must pass Object IDs to use across threads
‣Objects are locked for all operations including read‣Objects that feed the UI must be fetched on the main thread
Agenda
1)Core Data Basics2)Concurrency Problems3)Concurrency Solutions
Traditional Multi-Context
Pre-iOS 5: Thread Confinement‣Single NSMangedObjectContext per thread‣Manual notifications, merging, and saving‣Fairly easy to understand, but harder to manage
Traditional Multi-Context
Traditional Multi-Context- (void)loadData{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Create temp context NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:[(AppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator]]; // // Do lots of async work here // // Save the context. error = nil; if (![context save:&error]) { // Replace this implementation with code to handle the error appropriately. abort(); } });}
// Register for save notification in ViewDidLoad[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:context];
Traditional Multi-Context- (void)loadData{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Create temp context NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:[(AppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator]]; // // Do lots of async work here // // Save the context. error = nil; if (![context save:&error]) { // Replace this implementation with code to handle the error appropriately. abort(); } });}
// Register for save notification in ViewDidLoad[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:context];
Traditional Multi-Context- (void)loadData{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Create temp context NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init]; [context setPersistentStoreCoordinator:[(AppDelegate *)[[UIApplication sharedApplication] delegate] persistentStoreCoordinator]]; // // Do lots of async work here // // Save the context. error = nil; if (![context save:&error]) { // Replace this implementation with code to handle the error appropriately. abort(); } });}
// Register for save notification in ViewDidLoad[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:context];
Traditional Multi-Context- (void)contextDidSave:(NSNotification *)notification { dispatch_async(dispatch_get_main_queue(), ^{ NSManagedObjectContext *mainContext = [self.fetchedResultsController managedObjectContext]; [mainContext mergeChangesFromContextDidSaveNotification:notification]; });}
// Or
- (void)contextDidSave:(NSNotification *)notification { [self.managedObjectContext performSelectorOnMainThread: @selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];}
Traditional Multi-Context- (void)contextDidSave:(NSNotification *)notification { dispatch_async(dispatch_get_main_queue(), ^{ NSManagedObjectContext *mainContext = [self.fetchedResultsController managedObjectContext]; [mainContext mergeChangesFromContextDidSaveNotification:notification]; });}
// Or
- (void)contextDidSave:(NSNotification *)notification { [self.managedObjectContext performSelectorOnMainThread: @selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];}
Traditional Multi-Context
Code Examplehttps://github.com/mmorey/CoreDataMultiContext/tree/notification-context
Parent Child Context
≥ iOS 5: Parent Child Contexts‣Grand Central Dispatch private dispatch queues‣Threading managed for you, no manual synchronization required‣Less complicated and more flexible than pre-iOS 5 method ‣Context can and should be nested
Concurrency Types
‣NSConfinementConcurrencyType‣Separate contexts for each thread‣Default, Legacy option
‣NSPrivateQueueConcurrencyType‣MOC maintains private serialized queue‣Can be created from any other thread‣Idle queues are more efficient than extra threads
‣NSMainQueueConcurrencyType‣Similar to private queue but on the main queue
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
Parent Child Context
Parent Child Context
PSC PSC
Parent Child Context__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];temporaryContext.parentContext = managedObjectContext; [temporaryContext performBlock:^{
// // Do lots of async work here // // Save the context. NSError *error = nil; if (![temporaryContext save:&error]) {abort();} [managedObjectContext performBlock:^{ // Save the context. NSError *error = nil; if (![managedObjectContext save:&error]) {abort();}
}]; // main}]; // temp context
Parent Child Context__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];temporaryContext.parentContext = managedObjectContext; [temporaryContext performBlock:^{
// // Do lots of async work here // // Save the context. NSError *error = nil; if (![temporaryContext save:&error]) {abort();} [managedObjectContext performBlock:^{ // Save the context. NSError *error = nil; if (![managedObjectContext save:&error]) {abort();}
}]; // main}]; // temp context
Parent Child Context__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];temporaryContext.parentContext = managedObjectContext; [temporaryContext performBlock:^{
// // Do lots of async work here // // Save the context. NSError *error = nil; if (![temporaryContext save:&error]) {abort();} [managedObjectContext performBlock:^{ // Save the context. NSError *error = nil; if (![managedObjectContext save:&error]) {abort();}
}]; // main}]; // temp context
Private Queue save propagates up to parent
Parent Child Context__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];temporaryContext.parentContext = managedObjectContext; [temporaryContext performBlock:^{
// // Do lots of async work here // // Save the context. NSError *error = nil; if (![temporaryContext save:&error]) {abort();} [managedObjectContext performBlock:^{ // Save the context. NSError *error = nil; if (![managedObjectContext save:&error]) {abort();}
}]; // main}]; // temp context
Save to disc still locks PS which will block the UI during
read operations
Basics
AppAppAppAppAppApp
NSManagedObjectNSManagedObjectNSManagedObjectNSManagedObjectNSManagedObjectNSManagedObject
NSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContextNSManagedObjectContext
NSPersistentStoreCoordinatorNSPersistentStoreCoordinatorNSPersistentStoreCoordinator NSManagedObjectModelNSManagedObjectModelNSManagedObjectModel
NSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStoreNSPersistentStore
SQLite XML BinaryBinary In Memory Custom
Async Saving - Parent Child Context
Async Saving - Parent Child Context// Child context- (NSManagedObjectContext *)managedObjectContext{ if (_managedObjectContext != nil) { return _managedObjectContext; } _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; _managedObjectContext.parentContext = [self writerManagedObjectContext]; return _managedObjectContext;}
// Parent context- (NSManagedObjectContext *)writerManagedObjectContext{ if (_writerManagedObjectContext != nil) { return _writerManagedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; [_writerManagedObjectContext setPersistentStoreCoordinator:coordinator]; } return _writerManagedObjectContext;}
Async Saving - Parent Child Context// Child context- (NSManagedObjectContext *)managedObjectContext{ if (_managedObjectContext != nil) { return _managedObjectContext; } _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; _managedObjectContext.parentContext = [self writerManagedObjectContext]; return _managedObjectContext;}
// Parent context- (NSManagedObjectContext *)writerManagedObjectContext{ if (_writerManagedObjectContext != nil) { return _writerManagedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; [_writerManagedObjectContext setPersistentStoreCoordinator:coordinator]; } return _writerManagedObjectContext;}
Async Saving - Parent Child Context// Child context- (NSManagedObjectContext *)managedObjectContext{ if (_managedObjectContext != nil) { return _managedObjectContext; } _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; _managedObjectContext.parentContext = [self writerManagedObjectContext]; return _managedObjectContext;}
// Parent context- (NSManagedObjectContext *)writerManagedObjectContext{ if (_writerManagedObjectContext != nil) { return _writerManagedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { _writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType: NSPrivateQueueConcurrencyType]; [_writerManagedObjectContext setPersistentStoreCoordinator:coordinator]; } return _writerManagedObjectContext;}
Parent Child Context__block NSManagedObjectContext *writerObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] writerManagedObjectContext];__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];__block NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];temporaryContext.parentContext = managedObjectContext; [temporaryContext performBlock:^{ // // Do lots of async work here // [temporaryContext save:&error]; {abort();} // Save the context. [managedObjectContext performBlock:^{ [managedObjectContext save:&error]; {abort();} // Save the context.
[writerObjectContext performBlock:^{ [writerObjectContext save:&error]; {abort();} // Save the context. }]; // writer }]; // main}]; // temp context
Parent Child Context__block NSManagedObjectContext *writerObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] writerManagedObjectContext];__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];__block NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];temporaryContext.parentContext = managedObjectContext; [temporaryContext performBlock:^{ // // Do lots of async work here // [temporaryContext save:&error]; {abort();} // Save the context. [managedObjectContext performBlock:^{ [managedObjectContext save:&error]; {abort();} // Save the context.
[writerObjectContext performBlock:^{ [writerObjectContext save:&error]; {abort();} // Save the context. }]; // writer }]; // main}]; // temp context
NSMainQueueConcurrency
NSPrivateQueueConcurrencyType
Asynchronous Saving - Parent Child Context
Code Examplehttps://github.com/mmorey/CoreDataMultiContext/tree/parent-context
Still Blocking?
Still Blocking?
Small and frequent saves during import
Still Blocking?
Wait for opportunity when user won’t notice
Still Blocking?
- (void)setStalenessInterval:(NSTimeInterval)expiration
Still Blocking?
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"sections" cacheName:@"default-cache"];
Still Blocking?
For VERY LARGE amounts of data it may be better to generate the SQLite file on the
server, download it asynchronously, and set it up as an additional persistent store.
References
Nested MOC Release Notes: http://developer.apple.com/library/mac/#releasenotes/DataManagement/RN-CoreData/index.html
Core Data Programming Guide: http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/cdProgrammingGuide.html
Cocoanetics Blog:http://www.cocoanetics.com/2012/07/multi-context-coredata/
http://www.cocoanetics.com/2013/02/zarra-on-locking/
Thanks!
Questions? Get in Touch.Twitter: @xzolianApp.net: @morey
Email: [email protected]: http://matthewmorey.com