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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. Basics

    View 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 Slide

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

  9. Managed Object Model

    View Slide

  10. Managed Object Model

    View Slide

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

  12. Persistent Store Coordinator

    View 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 Slide

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

  15. Managed Object Context

    View Slide

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

  17. Single Context

    View Slide

  18. Single Context

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  24. Traditional Multi-Context

    View 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 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 Slide

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

    View 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 Slide

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

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

    View Slide

  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

    View Slide

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

  33. Parent Child Context

    View Slide

  34. Parent Child Context
    PSC PSC

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

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

    View Slide

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

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

  40. Async Saving - Parent Child Context

    View 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 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 Slide

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

    View Slide

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

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

    View Slide

  47. Still Blocking?

    View Slide

  48. Still Blocking?
    Small and frequent saves during import

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

  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/

    View Slide

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

    View Slide