42

I'm a beginner with Grand Central Dispatch (GCD) and Core Data, and I need your help to use Core Data with CGD, so that the UI is not locked while I add 40.000 records to Core Data.

I know that CD is not thread safe, so I have to use another context, and then save the data and merge contexts, as far as I was able to understand from some articles.

What I couldn't do yet, is put the pieces together.

So, in my code, I need your help on how to to that.

I have:

/*some other code*/

for (NSDictionary *memberData in arrayWithResult) {

    //get the Activities for this member
    NSArray *arrayWithMemberActivities = [activitiesDict objectForKey:[memberData objectForKey:@"MemberID"]];

    //create the Member, with the NSSet of Activities
    [Members createMemberWithDataFromServer:memberData
                         andActivitiesArray:arrayWithMemberActivities
                              andStaffArray:nil
                           andContactsArray:nil
                     inManagedObjectContext:self.managedObjectContext];
}

How can I transform this to work on the background, and then, when done saving, save the data and update the UI, without blocking the UI while saving the 40.000 objects?

Monolo
  • 18,205
  • 17
  • 69
  • 103
Rui Lopes
  • 2,562
  • 6
  • 34
  • 49

6 Answers6

56

Here's a good example for you to try. Feel free to come back if you have any questions:

self.mainThreadContext... // This is a reference to your main thread context
NSPersistentStoreCoordinator *mainThreadContextStoreCoordinator = [self.mainThreadContext persistentStoreCoordinator];
dispatch_queue_t request_queue = dispatch_queue_create("com.yourapp.DescriptionOfMethod", NULL);
dispatch_async(request_queue, ^{

    // Create a new managed object context
    // Set its persistent store coordinator
    NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
    [newMoc setPersistentStoreCoordinator:mainThreadContextStoreCoordinator]];

    // Register for context save changes notification
    NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
    [notify addObserver:self 
               selector:@selector(mergeChanges:) 
                   name:NSManagedObjectContextDidSaveNotification 
                 object:newMoc];

    // Do the work
    // Your method here
    // Call save on context (this will send a save notification and call the method below)
    BOOL success = [newMoc save:&error];
    if (!success)
        // Deal with error
    [newMoc release];
});
dispatch_release(request_queue);

And in response to the context save notification:

- (void)mergeChanges:(NSNotification*)notification 
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.mainThreadContext mergeChangesFromContextDidSaveNotification:notification waitUntilDone:YES];
    });
}

And don't forget to remove the observer from the notification center once you are done with the background thread context.

[[NSNotificationCenter defaultCenter] removeObserver:self];
Rog
  • 18,602
  • 6
  • 76
  • 97
  • great. Thanks. Just a little ');' missing before "dispatch_release(request_queue)". Thanks. – Rui Lopes Sep 25 '11 at 18:31
  • 1
    shouldn't we remove the observer after releasing the newMOC? – Rui Lopes Sep 25 '11 at 18:32
  • 1
    yup that sounds like a good idea. I have a helper method where I wrap my background processing tasks so the observer usually gets removed on dealloc of that class. – Rog Sep 25 '11 at 20:19
  • so, what's you're saying is that in the dealloc i should remove like this: [[NSNotificationCenter defaultCenter] removeObserver:self]; Can you update your answer so that's clear to others when viewing this? – Rui Lopes Sep 25 '11 at 20:48
  • @Rog is there a newer/better way to do this? I used your code but my UI is still locking-- I have looked into MagicalRecord as well and no matter what my UI is locked. – RyanG Jan 07 '13 at 20:14
  • See my answer below for my thoughts on this. – Matt S. Jul 23 '13 at 03:11
8

Here's a snippet which covers GCD and UI in it's simplest terms. You can replace doWork with your code that does the CoreData work.

Concerning CD and thread safety, one of the nice parts about GCD is you can sections off areas of your application (subsystems) to synchronize and ensure they get executed on the same queue. You could execute all CoreData work on a queue named com.yourcompany.appname.dataaccess.

In the sample, there's a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.

// on click of button
- (IBAction)doWork:(id)sender
{
    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    // async queue for bg work
    // main queue for updating ui on main thread
    dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    //  do the long running work in bg async queue
    // within that, call to update UI on main thread.
    dispatch_async(queue, 
                   ^{ 
                       [self performLongRunningWork]; 
                       dispatch_async(main, ^{ [self workDone]; });
                   });

    // release queues created.
    dispatch_release(queue);    
}

- (void)performLongRunningWork
{
    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);
}

- (void)workDone
{
    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];
}
bryanmac
  • 38,941
  • 11
  • 91
  • 99
  • your example is cool, but not specify core data concurrence. Thanks anyway. – Rui Lopes Sep 25 '11 at 18:30
  • the point was that the queue handles the concurrency if you partition subsystems in your app an ensure all the async work queued for that subsystem is using the same queue. – bryanmac Sep 25 '11 at 20:09
  • From the post above: "Concerning CD and thread safety, one of the nice parts about GCD is you can sections off areas of your application (subsystems) to synchronize and ensure they get executed on the same queue. You could execute all CoreData work on a queue named com.yourcompany.appname.dataaccess." – bryanmac Sep 25 '11 at 20:10
  • @bryanmac +1 for the example on how to get a reference to the main thread for UI updates. Also don't forget to release queue as you have created it yourself with dispatch_queue_create. – Rog Sep 25 '11 at 21:06
  • updated with release in code and concurrency note is in post. – bryanmac Sep 26 '11 at 20:23
3

This blog post has a detailed description on Core Data concurrency and sample code: http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

Michael
  • 447
  • 3
  • 10
1

Adding another source of info you can check

ThreadedCoreData

the Sample Code of Apple's iOS Developer Library, which have been recently updated (2013-06-09)

Demonstrates how to use Core Data in a multi-threaded environment, following the first recommended pattern mentioned in the Core Data Programming Guide.

Based on the SeismicXML sample, it downloads and parses an RSS feed from the United States Geological Survey (USGS) that provides data on recent earthquakes around the world. What makes this sample different is that it persistently stores earthquakes using Core Data. Each time you launch the app, it downloads new earthquake data, parses it in an NSOperation which checks for duplicates and stores newly founded earthquakes as managed objects.

For those new to Core Data, it can be helpful to compare SeismicXML sample with this sample and notice the necessary ingredients to introduce Core Data in your application.

angelos.p
  • 500
  • 1
  • 5
  • 12
0

So the selected answer for this is from nearly 2 years ago now, and there's a few issues with it:

  1. It's not ARC friendly - need to remove release call on newMoc - ARC won't even compile with that
  2. You should be doing the weakSelf / strongSelf dance inside the block - otherwise you're probably creating a retain loop on the observer creation. See Apple's doc's here: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
  3. @RyanG asked in a comment why he's blocking. My guess is because the recently edited method has waitUntilDone:YES - except that's going to block the main thread. You probably want waitUntilDone:NO but I don't know if there's UI updates firing from these change events as well so it would require testing.

--Edit--

Looking further into #3 - waitUntilDone:YES isn't a valid methodSignature for managed context objects, so how does that even work?

Matt S.
  • 1,882
  • 13
  • 17
0

Much easier way to do it than attach the persistent store coordinator to a new context, which is not thread safe either, btw.

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrency];
[context setParentContext:<main thread context here>];

[context performBlock:^{

    ...
    // Execute all code on current context
    ...

}];

NSError *error = nil;
[context save:&error];
if (!error) {
    [context.parentContext save:&error];
    if (error) {
        NSLog(@"Could not save parent context: %@", error);
    }
}
else {
    NSLog(@"Could not save context: %@", error);
}

Great tutorial on how to use multi-context Core Data:

http://www.cocoanetics.com/2012/07/multi-context-coredata/

Simon Germain
  • 6,834
  • 1
  • 27
  • 42