It has been mentioned several times throughout the past two chapters that you can use multiple managed object contexts. In many cases, you will only need a single managed object context. Using a single managed object context means that all of the code related to the managed object context is executed on the main thread. If you're performing small operations, that's fine. However, imagine importing large amounts of data. An operation like that could take a while. Executing code that runs for a while on the main thread will cause the user interface to become unresponsive. This is not good, as the user will think your app has crashed. So how do you work around this? The answer is using multiple managed object contexts.
In the past, using several managed object contexts was not easy to manage, you had to create instances of NSManagedObjectContext using the correct queues yourself. Luckily, the NSPersistentContainer helps us manage more complex setups. If you want to import data on a background task, you can either obtain a managed object context by calling newBackgroundContext() on the persistent container, or you can use this context to perform tasks, for instance a data import, in the background. If you don't need a reference to the background context, you could call performBackgroundTask and pass it a closure with the processing you want to do in the background.
One important thing to understand about core data, background tasks, and multithreading is that you must always use a managed object context on the same thread it was created on. Imagine the following example:
let backgroundQueue = DispatchQueue(label: "backgroundQueue")
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundQueue.async {
let results = try? backgroundContext.fetch(someRequest)
for result in results {
// use result
}
}
The preceding behavior of the code can cause you a couple of headaches. The background context was created on a different queue than the one we're using it on. It's always best to make sure to use the context on the same queue it was created on by using theĀ NSManagedObject's perform(_:) method. More importantly, you must also make sure to use the managed objects you retrieve on the same queue that the managed object context belongs to.
Often, you'll find that it's best to fetch data on the main queue using the managed object context that lives in the main thread. Storing data can be delegated to background contexts if needed. If you do this, you must make sure that the background context is a child context of the main context. When this relationship is defined between the two contexts, your main context will automatically receive updates when the background context is persisted. This is quite convenient because it removes a lot of manual maintenance which keeps your contexts in sync.
When you find that your app requires a setup with multiple managed object contexts, it's important to keep the rules mentioned in this section in mind. Bugs related to using managed objects or managed object contexts in the wrong places are often tedious to debug and hard to discover. Your code can run fine for weeks and suddenly start crashing or behaving unreliably. Therefore, if you don't need multiple contexts, it might be better to avoid them for a while until you have a solid understanding of Core Data. Learning Core Data and debugging concurrency troubles are not somethings that you're going to want to learn at the same time.