Using Core Data concurrently on the main thread and on background threads can help keep iOS and macOS apps responsive. This post presents an overview of Core Data concurrency and various examples of using Core Data in the background:

  1. Core Data Concurrency
    a. Is Core Data Thread Safe?
    b. .mainQueueConcurrencyType
    c. .privateQueueConcurrencyType
    d. NSManagedObjectContext.perform
    e. NSManagedObjectContext.performAndWait
  2. Core Data Background Context
  3. Core Data Background Fetch Request
  4. Save Core Data On A Background Thread
  5. Create A New Core Data Object On A Background Thread

Note: in this post persistentContainer will be used to refer to an initialized instance of NSPersistentContainer, and object will be used to refer to an initialized instance of NSManagedObject.

Core Data Concurrency

Core Data has built-in interfaces for managing operations across different threads. The most important things to keep in mind when supporting Core Data concurrency are:

  1. A NSManagedObjectContext must perform operations on the thread it was created
  2. The NSManagedObjectContext perform(_:) and performAndWait(_:) functions are thread safe and can be called from other threads
  3. A NSManagedObject cannot be shared across threads
  4. A NSManagedObjectID can be shared across threads
  5. Changes from one NSManagedObjectContext can be merged into another NSManagedObjectContext, even on another thread

Note: using a NSPersistentContainer may simplify the code required to support Core Data concurrency, as shown in the examples in this post.

Is Core Data Thread Safe?

Only some objects and functions in Core Data are thread safe. For example, NSManagedObject is not thread safe. However, NSManagedObjectID is thread safe. One way to get access to the same NSManagedObject across threads is to pass the NSManagedObjectID across threads and read the object:

let objectID = // a NSManagedObjectID

// Read the object using a managed object context
try context.existingObject(with: objectID)

Note: newly created Core Data objects have temporary object ids until the object is saved for the first time. Temporary object ids cannot be used to read objects on other threads.

Another example, NSManagedObjectContext contains both thread safe and not thread safe functions. For example, save() and fetch(_:) are not thread safe. However, perform(_:) and performAndWait(_:) are thread safe.

.mainQueueConcurrencyType

The .mainQueueConcurrencyType concurrency type is used for a NSManagedObjectContext on the main thread. A NSPersistentContainer has a property, viewContext, that is a reference to a NSManagedObjectContext with a .mainQueueConcurrencyType concurrency type:

// Get the main thread NSManagedObjectContext 
// from a NSPersistentContainer
let context = persistentContainer.viewContext

If needed, a NSManagedObjectContext with a .mainQueueConcurrencyType concurrency type can be created directly:

// Use NSManagedObjectContext to directly create 
// a main thread context
let context = NSManagedObjectContext(
    concurrencyType: .mainQueueConcurrencyType
)

// Set the persistent store coordinator so the 
// managed object context knows where to save 
// to and read from
context.persistentStoreCoordinator = 
    // NSPersistentStoreCoordinator

.privateQueueConcurrencyType

A background NSManagedObjectContext has a concurrency type of .privateQueueConcurrencyType. Two common approaches to create a background NSManagedObjectContext with a .privateQueueConcurrencyType are using a NSPersistentContainer and directly with NSManagedObjectContext:

// Use newBackgroundContext() with a 
// NSPersistentContainer to create a 
// background NSManagedObjectContext
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context 
// stays up to date with changes from 
// the parent
context.automaticallyMergesChangesFromParent = true
// Use NSManagedObjectContext to directly 
// create a background context
let context = NSManagedObjectContext(
    concurrencyType: .privateQueueConcurrencyType
)

// Set the parent context of the background context
context.parent = // NSManagedObjectContext

// If needed, ensure the background context 
// stays up to date with changes from 
// the parent
context.automaticallyMergesChangesFromParent = true

NSManagedObjectContext.perform

The perform(_:) function is thread safe and will run the provided code block asynchronously on the thread the NSManagedObjectContext was created on.

// Run the block asynchronously on the thread 
// the context was created on
context.perform {
    // Perform background Core Data operations
    // ...
}

// Code after perform(_:) may run before the
// provided code block

NSManagedObjectContext.performAndWait

Like perform(_:), performAndWait(_:) is thread safe and will run the provided code block on the thread the NSManagedObjectContext was created on. Unlike perform(_:), performAndWait(_:) will run the provided code block synchronously and wait for the block to complete:

// Run the block synchronously on the thread 
// the context was created on
context.performAndWait {
    // Perform background Core Data operations    
    // ...
}

// Code after performAndWait(_:) will run 
// after the provided code block

Core Data Background Context

There are multiple ways to use Core Data and a NSManagedObjectContext on a background thread. One way is the performBackgroundTask(_:) method on an NSPersistentContainer:

// Spawns a new thread and creates a background 
// managed object context to perform operations 
// in the background
persistentContainer.performBackgroundTask { 
    (context) in
    // Perform background Core Data operations
    // ...
}

A drawback of performBackgroundTask(_:) is that performBackgroundTask(_:) creates a new thread. Calling performBackgroundTask(_:) repeatedly may negatively impact performance. Instead, newBackgroundContext() on a NSPersistentContainer can be used to create a background NSManagedObjectContext:

// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context stays 
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

In some cases a NSPersistentContainer may not be available. A background NSManagedObjectContext can also be created explicitly:

// Create a new background context
let context = NSManagedObjectContext(
    concurrencyType: .privateQueueConcurrencyType
)

// Set the parent context
context.parent = // NSManagedObjectContext

// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

Core Data Background Fetch Request

To get objects from a fetch request on a background thread, use perform(_:) or performAndWait(_:) on a background NSManagedObjectContext:

// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

// Perform operations on the background context
// asynchronously
context.perform {
    do {
        // Create a fetch request
        let fetchRequest: NSFetchRequest<CustomEntity>

        fetchRequest = CustomEntity.fetchRequest()
        fetchRequest.fetchLimit = 1

        let objects = try context.fetch(fetchRequest)

        // Handle fetched objects
    }
    catch let error {
        // Handle error
    }
}

To learn more about NSFetchRequest and retrieving objects read the following article:

Core Data Fetch Requests
Learn how to make queries using fetch requests in Core Data for objects by ID, predicate, and entity with Swift.

Save Core Data On A Background Thread

Use perform(_:) or performAndWait(_:) to ensure save() is called on the background thread a managed object context was created on:

// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

// Perform operations on the background context
// asynchronously
context.perform {
    do {
        // Modify the background context
        // ...
        
        // Save the background context
        try context.save()
    }
    catch let error {
        // Handle error
    }
}

Create A New Core Data Object On A Background Thread

To create a new core data object in the background, use perform(_:) or performAndWait(_:) on a background NSManagedObjectContext:

// Create a new background managed object context
let context = persistentContainer.newBackgroundContext()

// If needed, ensure the background context stays
// up to date with changes from the parent
context.automaticallyMergesChangesFromParent = true

// Perform operations on the background context
// asynchronously
context.perform {
    do {
        // Create A New Object
        let object = CustomEntity(context: context)
        
        // Configure object properties
        // ...

        // Save the background context
        try context.save()
    }
    catch let error {
        // Handle errors
    }
}

Core Data Thread Safety in Swift

That’s it! By using NSPersistentContainer and a background NSManagedObjectContext you can create new objects, perform fetch requests, and save objects in the background using Core Data in Swift.