Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grand Central Dispatch (GCD) with CoreData

Tags:

I'm using Grand Central Dispatch (GCD) in my application to do some heavy lifting. The application is using Core-Data for data storage purposes. Here's my scenario (along with relevant question):

dispatch_queue_t main_queue = dispatch_get_main_queue(); dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);  dispatch_async(request_queue, ^{     MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];      // …      // <heavy lifting>     // …       // …      // <update mObject>     // …       [self saveManagedObjectContext]; });      

As a result of [self saveManagedObjectContext], fetchResultsController delegate methods are called automatically. Consequently, the UI updation logic kicks.

Now my question is, do I need to use main_queue for -saveManagedObjectContext? Should I perform all operations on my NSManagedObject in main_queue? Some of the operations that update the NSManagedObject might take 2-3 seconds. Please advise.

like image 248
Mustafa Avatar asked Nov 24 '10 07:11

Mustafa


People also ask

Is Coredata thread safe?

Core Data is designed to work in a multithreaded environment. However, not every object under the Core Data framework is thread safe. To use Core Data in a multithreaded environment, ensure that: Managed object contexts are bound to the thread (queue) that they are associated with upon initialization.

What is GCD in Swift 5?

Grand Central Dispatch (GCD) is a low-level API for managing concurrent operations. It can help improve your app's responsiveness by deferring computationally expensive tasks to the background. It's an easier concurrency model to work with than locks and threads.

How Grand Central Dispatch works?

GCD works by allowing specific tasks in a program that can be run in parallel to be queued up for execution and, depending on availability of processing resources, scheduling them to execute on any of the available processor cores (referred to as "routing" by Apple).

What is GCD in iOS?

Overview. Dispatch, also known as Grand Central Dispatch (GCD), contains language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.


2 Answers

There is a golden rule when it comes to Core Data - one Managed Object Context per thread. Managed object contexts are not thread safe so if you are doing work in a background task you either use the main thread to avoid threading conflicts with UI operations, or you create a new context to do the work in. If the work is going to take a few seconds then you should do the latter to stop your UI from locking up.

To do this you create a new context and give it the same persistent store as your main context:

NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease]; [backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]]; 

Do whatever operations you need to do, then when you save that new context you need to handle the save notification and merge the changes into your main context with the mergeChangesFromContextDidSaveNotification: message. The code should look something like this:

/* Save notification handler for the background context */ - (void)backgroundContextDidSave:(NSNotification *)notification {     /* Make sure we're on the main thread when updating the main context */     if (![NSThread isMainThread]) {         [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)                                withObject:notification                             waitUntilDone:NO];         return;     }      /* merge in the changes to the main context */     [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; }  /* ... */  /* Save the background context and handle the save notification */ [[NSNotificationCenter defaultCenter] addObserver:self                                          selector:@selector(backgroundContextDidSave:)                                              name:NSManagedObjectContextDidSaveNotification                                            object:backgroundContext];  [backgroundContext save:NULL];  [[NSNotificationCenter defaultCenter] removeObserver:self                                                 name:NSManagedObjectContextDidSaveNotification                                               object:syncContext]; 

Handling the save notifcation and merging is important otherwise your main UI/context won't see the changes you made. By merging, your main fetchResultsController etc. will get change events and update your UI as you would expect.

Another important thing to note is that NSManagedObject instances can only be used in the context that they were fetched from. If your operation needs a reference to an object then you have to pass the object's objectID to the operation and re-fetch an NSManagedObject instance from the new context using existingObjectWithID:. So something like:

/* This can only be used in operations on the main context */ MyNSManagedObject *objectInMainContext =     [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];  /* This can now be used in your background context */ MyNSManagedObject *objectInBackgroundContext =     (MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]]; 
like image 65
Mike Weller Avatar answered Sep 24 '22 06:09

Mike Weller


As you probably know or have noticed you must perform UI operations on the main thread. As you mention it is when you save the UI update takes place. You can solve this by nesting a call to dispatch_sync on the main thread.

dispatch_queue_t main_queue = dispatch_get_main_queue(); dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);  __block __typeof__(self) blockSelf = self;  dispatch_async(request_queue, ^{     MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];      // update and heavy lifting...      dispatch_sync(main_queue, ^{       [blockSelf saveManagedObjectContext];     }); });      

The use of blockSelf is to avoid creating accidentally reference cycles. (Practical blocks)

like image 34
Robert Höglund Avatar answered Sep 21 '22 06:09

Robert Höglund