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.
21. 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
23. 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
25. 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];
26. 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];
27. 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];
30. Traditional Multi-Context
Code Example
https://github.com/mmorey/CoreDataMultiContext/tree/notification-context
31. 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
32. Concurrency Types
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
‣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
35. 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
36. 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
37. Parent Child Context
__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication]
delegate] managedObjectContext];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = managedObjectContext;
[temporaryContext performBlock:^{
Private Queue save
//
// Do lots of async work here
//
propagates up to parent
// 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
38. Parent Child Context
__block NSManagedObjectContext *managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication]
delegate] managedObjectContext];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = managedObjectContext;
[temporaryContext performBlock:^{ Save to disc still locks PS
// which will block the UI during
// Do lots of async work here
// read operations
// 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
39. Basics
App
NSManagedObject
NSManagedObjectContext
NSPersistentStoreCoordinator NSManagedObjectModel
NSPersistentStore
SQLite XML Binary In Memory Custom
52. 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.
53. 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/
54. Thanks!
Questions? Get in Touch.
Twitter: @xzolian
App.net: @morey
Email: matt@matthewmorey.com
URL: http://matthewmorey.com