Slide 1

Slide 1 text

Core Data with multiple managed object contexts Photo by Free-Photo-Gallery.org

Slide 2

Slide 2 text

Core Data with multiple managed object contexts Photo by Free-Photo-Gallery.org

Slide 3

Slide 3 text

Matt(hew) Morey matthewmorey.com | @xzolian Senior Developer at ChaiONE Traveler Boardsport Junkie

Slide 4

Slide 4 text

Agenda 1)Core Data Basics 2)Concurrency Problems 3)Concurrency Solutions

Slide 5

Slide 5 text

Agenda 1)Core Data Basics 2)Concurrency Problems 3)Concurrency Solutions

Slide 6

Slide 6 text

Basics

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Managed Object Model

Slide 10

Slide 10 text

Managed Object Model

Slide 11

Slide 11 text

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; }

Slide 12

Slide 12 text

Persistent Store Coordinator

Slide 13

Slide 13 text

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; }

Slide 14

Slide 14 text

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; }

Slide 15

Slide 15 text

Managed Object Context

Slide 16

Slide 16 text

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; }

Slide 17

Slide 17 text

Single Context

Slide 18

Slide 18 text

Single Context

Slide 19

Slide 19 text

Single Context Code Example https://github.com/mmorey/CoreDataMultiContext/tree/blocking

Slide 20

Slide 20 text

Agenda 1)Core Data Basics 2)Concurrency Problems 3)Concurrency Solutions

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Agenda 1)Core Data Basics 2)Concurrency Problems 3)Concurrency Solutions

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Traditional Multi-Context

Slide 25

Slide 25 text

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];

Slide 26

Slide 26 text

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];

Slide 27

Slide 27 text

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];

Slide 28

Slide 28 text

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]; }

Slide 29

Slide 29 text

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]; }

Slide 30

Slide 30 text

Traditional Multi-Context Code Example https://github.com/mmorey/CoreDataMultiContext/tree/notification-context

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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];

Slide 33

Slide 33 text

Parent Child Context

Slide 34

Slide 34 text

Parent Child Context PSC PSC

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Async Saving - Parent Child Context

Slide 41

Slide 41 text

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; }

Slide 42

Slide 42 text

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; }

Slide 43

Slide 43 text

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; }

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Asynchronous Saving - Parent Child Context Code Example https://github.com/mmorey/CoreDataMultiContext/tree/parent-context

Slide 47

Slide 47 text

Still Blocking?

Slide 48

Slide 48 text

Still Blocking? Small and frequent saves during import

Slide 49

Slide 49 text

Still Blocking? Wait for opportunity when user won’t notice

Slide 50

Slide 50 text

Still Blocking? - (void)setStalenessInterval:(NSTimeInterval)expiration

Slide 51

Slide 51 text

Still Blocking? NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"sections" cacheName:@"default-cache"];

Slide 52

Slide 52 text

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.

Slide 53

Slide 53 text

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/

Slide 54

Slide 54 text

Thanks! Questions? Get in Touch. Twitter: @xzolian App.net: @morey Email: [email protected] URL: http://matthewmorey.com