Core Data with multiple managed object contexts

Post on 12-May-2015

20.531 views 0 download

Tags:

description

When using Core Data for persisting app data multiple managed object contexts (MOC) are often required to avoid blocking UI. Typically you would create a background MOC and listen for changes on the main MOC, merging changes as necessary. With iOS 5, MOCs now have parent context and the ability to set concurrency types. These new features greatly simplify dealing with Core Data on background queues. During this presentation Matt will cover the pros and cons of this new method of dealing with Core Data.

Transcript of Core Data with multiple managed object contexts

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: matt@matthewmorey.comURL: http://matthewmorey.com