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. Core Data
    with multiple managed
    object contexts
    Photo by Free-Photo-Gallery.org

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. 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

    View full-size slide

  7. 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

    View full-size slide

  8. Managed Object Model

    View full-size slide

  9. Managed Object Model

    View full-size slide

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

    View full-size slide

  11. Persistent Store Coordinator

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  14. Managed Object Context

    View full-size slide

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

    View full-size slide

  16. Single Context

    View full-size slide

  17. Single Context

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  20. 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

    View full-size slide

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

    View full-size slide

  22. 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

    View full-size slide

  23. Traditional Multi-Context

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. 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

    View full-size slide

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

    View full-size slide

  32. Parent Child Context

    View full-size slide

  33. Parent Child Context
    PSC PSC

    View full-size slide

  34. 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

    View full-size slide

  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

    View full-size slide

  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
    Private Queue save
    propagates up to parent

    View full-size slide

  37. 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

    View full-size slide

  38. 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

    View full-size slide

  39. Async Saving - Parent Child Context

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  43. 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

    View full-size slide

  44. 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

    View full-size slide

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

    View full-size slide

  46. Still Blocking?

    View full-size slide

  47. Still Blocking?
    Small and frequent saves during import

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  51. 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.

    View full-size slide

  52. 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/

    View full-size slide

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

    View full-size slide