Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Core Data with Multiple Managed Object Contexts

Core Data with Multiple Managed Object Contexts

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.

Matthew Morey

March 31, 2013
Tweet

More Decks by Matthew Morey

Other Decks in Technology

Transcript

  1. Basics App App App App App App NSManagedObject NSManagedObject NSManagedObject

    NSManagedObject NSManagedObject NSManagedObject NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSPersistentStoreCoordinator NSPersistentStoreCoordinator NSPersistentStoreCoordinator NSManagedObjectModel NSManagedObjectModel NSManagedObjectModel NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore SQLite XML Binary Binary In Memory Custom
  2. Basics App App App App App App NSManagedObject NSManagedObject NSManagedObject

    NSManagedObject NSManagedObject NSManagedObject NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSPersistentStoreCoordinator NSPersistentStoreCoordinator NSPersistentStoreCoordinator NSManagedObjectModel NSManagedObjectModel NSManagedObjectModel NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore SQLite XML Binary Binary In Memory Custom
  3. 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; }
  4. 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; }
  5. 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; }
  6. 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; }
  7. 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
  8. 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
  9. 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];
  10. 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];
  11. 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];
  12. 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]; }
  13. 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]; }
  14. 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
  15. 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];
  16. 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
  17. 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
  18. 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
  19. 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
  20. Basics App App App App App App NSManagedObject NSManagedObject NSManagedObject

    NSManagedObject NSManagedObject NSManagedObject NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSManagedObjectContext NSPersistentStoreCoordinator NSPersistentStoreCoordinator NSPersistentStoreCoordinator NSManagedObjectModel NSManagedObjectModel NSManagedObjectModel NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore NSPersistentStore SQLite XML Binary Binary In Memory Custom
  21. 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; }
  22. 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; }
  23. 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; }
  24. 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
  25. 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
  26. 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.
  27. 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/