johnMa
johnMa

Reputation: 3311

CoreData and thread safety

I have a singleton name CoreDataManager which register the mergeContextChangesForNotification in it:

+ (id) sharedManager{
    static CoreDataManager *mSharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mSharedManager = [[CoreDataManager alloc] init];
    });
    return mSharedManager;
}

- (id)init
{
    self = [super init];
    if (self) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(mergeContextChangesForNotification:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:nil];
        });
    }
    return self;
}

after i receive the notification:

- (void)mergeContextChangesForNotification:(NSNotification *)notification {
        [shareContext  performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                                        withObject:notification 
                                     waitUntilDone:YES];
}

I have two question here:

  1. Should i use performSelectorOnMainThread here? since this answer says never.should i change it to GCD and use dispatch_get_main_queue??
  2. Does the registration of mergeContextChangesForNotification in init is a good practice to ensure notification always register in main thread? which i read from this answer

Upvotes: 3

Views: 4391

Answers (2)

Martin R
Martin R

Reputation: 539965

With the managed object concurrency types introduced in iOS 5/OS X 10.7 it is preferred to use the performBlock methods to ensure that a Core Data operation is executed on the right thread (more precisely: on the right queue).

So you would create the shared context with

shareContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

and merge changes from other contexts with

- (void)mergeContextChangesForNotification:(NSNotification *)notification {
    [shareContext performBlock:^{
        [shareContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

Note also that (as stated in the NSManagedObjectContext documentation), it is recommended to register for save notifications only from known contexts. With object:nil in your registration you might get unexpected notifications because system frameworks use Core Data internally.

So you should either register only for the contexts you create. Alternatively, you can check that a notification was sent from a context with the same persistent store coordinator:

- (void)mergeContextChangesForNotification:(NSNotification *)notification {
     NSManagedObjectContext *otherContext = [notification object];
     if (otherContext != shareContext &&
         [otherContext persistentStoreCoordinator] == [shareContext persistentStoreCoordinator]) {
              [shareContext performBlock:^{
                   [shareContext mergeChangesFromContextDidSaveNotification:notification];
              }];
    }
}

Finally, a notification method is always called on the thread on which it it posted. It does not matter on which thread the notification is registered. Therefore dispatching the registration to the main thread is not necessary.

Upvotes: 8

A-Live
A-Live

Reputation: 8944

Check TopSongs sample application, keep in mind they are not perfect but most of the time may be used for reference. They are synchronizing mergeChangesFromContextDidSaveNotification calls to be made on the main thread only but doing it in a little more elegant way:

// This method will be called on a secondary thread. Forward to the main thread for safe handling of UIKit objects.
- (void)importerDidSave:(NSNotification *)saveNotification {

    if ([NSThread isMainThread]) {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
        [self.songsViewController fetch];
    } else {
        [self performSelectorOnMainThread:@selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
    }
}

As for initialization, -init will be called on the same thread +sharedManager was called.

Also, since the second answer you linked is not very informative in terms of documentation, let me leave a link to Concurrency with Core Data section of the docs.

Upvotes: 1

Related Questions