Documentation Archive

Developer

Core Data Programming Guide

On This Page

Concurrency

Concurrency is the ability to work with the data on more than one queue at the same time. If you choose to use concurrency with Core Data, you also need to consider the application environment. For the most part, AppKit and UIKit are not thread-safe. In macOS in particular, Cocoa bindings and controllers are not threadsafe—if you are using these technologies, multithreading may be complex.

Core Data, Multithreading, and the Main Thread

In Core Data, the managed object context can be used with two concurrency patterns, defined by NSMainQueueConcurrencyType and NSPrivateQueueConcurrencyType.

NSMainQueueConcurrencyType is specifically for use with your application interface and can only be used on the main queue of an application.

The NSPrivateQueueConcurrencyType configuration creates its own queue upon initialization and can be used only on that queue. Because the queue is private and internal to the NSManagedObjectContext instance, it can only be accessed through the performBlock: and the performBlockAndWait: methods.

In both cases, the initialization of the NSManagedObjectContext instance is the same:

Objective-C

  1. NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:<#type#>];

Swift

  1. let moc = NSManagedObjectContext(concurrencyType:<#type#>)

The parameter being passed in as part of the initialization determines what type of NSManagedObjectContext is returned.

When you are using an NSPersistentContainer, the viewContext property is configured as a NSMainQueueConcurrencyType context and the contexts associated with performBackgroundTask: and newBackgroundContext are configured as NSPrivateQueueConcurrencyType.

Using a Private Queue to Support Concurrency

In general, avoid doing data processing on the main queue that is not user-related. Data processing can be CPU-intensive, and if it is performed on the main queue, it can result in unresponsiveness in the user interface. If your application will be processing data, such as importing data into Core Data from JSON, create a private queue context and perform the import on the private context. The following example shows how to do this:

Objective-C

  1. NSArray *jsonArray = ; //JSON data to be imported into Core Data
  2. NSManagedObjectContext *moc = ; //Our primary context on the main queue
  3. NSManagedObjectContext *private = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
  4. [private setParentContext:moc];
  5. [private performBlock:^{
  6. for (NSDictionary *jsonObject in jsonArray) {
  7. NSManagedObject *mo = ; //Managed object that matches the incoming JSON structure
  8. //update MO with data from the dictionary
  9. }
  10. NSError *error = nil;
  11. if (![private save:&error]) {
  12. NSLog(@"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]);
  13. abort();
  14. }
  15. [moc performBlockAndWait:^{
  16. NSError *error = nil;
  17. if (![moc save:&error]) {
  18. NSLog(@"Error saving context: %@\n%@", [error localizedDescription], [error userInfo]);
  19. abort();
  20. }
  21. }];
  22. }];

Swift

  1. let jsonArray = … //JSON data to be imported into Core Data
  2. let moc = … //Our primary context on the main queue
  3. let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
  4. privateMOC.parentContext = moc
  5. privateMOC.performBlock {
  6. for jsonObject in jsonArray {
  7. let mo = … //Managed object that matches the incoming JSON structure
  8. //update MO with data from the dictionary
  9. }
  10. do {
  11. try privateMOC.save()
  12. moc.performBlockAndWait {
  13. do {
  14. try moc.save()
  15. } catch {
  16. fatalError("Failure to save context: \(error)")
  17. }
  18. }
  19. } catch {
  20. fatalError("Failure to save context: \(error)")
  21. }
  22. }

In this example an array of data has been originally received as a JSON payload. You then create a new NSManagedObjectContext that is defined as a private queue. The new context is set as a child of the main queue context that runs the application. From there you call performBlock: and do the actual NSManagedObject creation inside of the block that is passed to performBlock:. After all of the data has been consumed and turned into NSManagedObject instances, you call save on the private context, which moves all of the changes into the main queue context without blocking the main queue.

This example can be further simplified when using an NSPersistentContainer:

Objective-C

  1. NSArray *jsonArray = ;
  2. NSPersistentContainer *container = self.persistentContainer;
  3. [container performBackgroundTask:^(NSManagedObjectContext *context) {
  4. for (NSDictionary *jsonObject in jsonArray) {
  5. AAAEmployeeMO *mo = [[AAAEmployeeMO alloc] initWithContext:context];
  6. [mo populateFromJSON:jsonObject];
  7. }
  8. NSError *error = nil;
  9. if (![context save:&error]) {
  10. NSLog(@"Failure to save context: %@\n%@", [error localizedDescription], [error userInfo]);
  11. abort();
  12. }
  13. }];

Swift

  1. let jsonArray = …
  2. let container = self.persistentContainer
  3. container.performBackgroundTask() { (context) in
  4. for jsonObject in jsonArray {
  5. let mo = EmployeeMO(context: context)
  6. mo.populateFromJSON(jsonObject)
  7. }
  8. do {
  9. try context.save()
  10. } catch {
  11. fatalError("Failure to save context: \(error)")
  12. }
  13. }

Passing References Between Queues

NSManagedObject instances are not intended to be passed between queues. Doing so can result in corruption of the data and termination of the application. When it is necessary to hand off a managed object reference from one queue to another, it must be done through NSManagedObjectID instances.

You retrieve the managed object ID of a managed object by calling the objectID method on the NSManagedObject instance.