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:
- Core Data Concurrency
a. Is Core Data Thread Safe?
b. .mainQueueConcurrencyType
c. .privateQueueConcurrencyType
d. NSManagedObjectContext.perform
e. NSManagedObjectContext.performAndWait - Core Data Background Context
- Core Data Background Fetch Request
- Save Core Data On A Background Thread
- 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:
- A
NSManagedObjectContext
must perform operations on the thread it was created - The
NSManagedObjectContext
perform(_:)
andperformAndWait(_:)
functions are thread safe and can be called from other threads - A
NSManagedObject
cannot be shared across threads - A
NSManagedObjectID
can be shared across threads - Changes from one
NSManagedObjectContext
can be merged into anotherNSManagedObjectContext
, 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:
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.