Earlier this month, I wrote about Core Data faults. Faulting is a key concept of the Core Data framework. It is because of faulting that Core Data is as performant as it is and it ensures Core Data's memory footprint remains at an acceptable level.
But faulting can sometimes lead to unexpected problems. If the fault's underlying data in the persistent store is deleted, Core Data can no longer fulfill the fault. In earlier versions of the framework, an exception would be thrown with your application crashing as a result.
As of iOS 9 and macOS 10.11, you can avoid this problem by setting the shouldDeleteInaccessibleFaults
property of a managed object context to true
. If a fault for a deleted managed object is fired, the managed object context fails silently by marking the managed object as deleted.
While this prevents an exception from being thrown, your application may not know how to deal with an inaccessible fault. How should the user interface respond to data that is no longer present in the persistent store?
Core Data Query Generations
This has been an issue for as long as Core Data exists. Fortunately, the Core Data team has finally come up with a solution, query generations.
Query generations are available in iOS 10 and macOS 10.12. As the name suggests, a query generation is a snapshot of the data in the persistent store. A managed object context can choose to pin itself to a query generation, which means that it interacts with a snapshot of the data in the persistent store.
The managed object context provides a window into that query generation. No matter what happens to the data in the persistent store, the managed object context continues to see and interact with the data of the query generation.
For applications that use multiple managed object contexts, each managed object context can work in isolation. Changes made by one managed object context don't necessarily affect other managed object contexts.
Pinning to a Query Generation
Unpinned
By default, a managed object context doesn't pin itself to a query generation. The managed object context is unpinned. This means it always interacts with the current query generation. This is identical to how Core Data operates before query generations were introduced.
Pinned When Loaded
Another option is to pin the managed object context to a query generation the moment it loads data from the persistent store. At that moment, the managed object context pins itself to the current query generation.
Pinned to a Specific Query Generation
Every query generation has a unique token. This token allows a managed object context to pin itself to a specific query generation. To find out what query generation a managed object context is pinned to, you ask for the value of its queryGenerationToken
property.
managedObjectContext.queryGenerationToken
To pin a managed object context to a query generation, you invoke the setQueryGenerationFrom(_:)
method, passing in an instance of the NSQueryGenerationToken
class.
managedObjectContext.setQueryGenerationFrom(queryGenerationToken)
What About Nested Managed Object Contexts
A child managed object context is unpinned. Why is that? The parent managed object context decides which query generation it is pinned to. The children of the parent managed object context work with the data of the query generation the parent managed object context is pinned to. That makes sense. Right?
Changing Query Generations
At some point, the managed object context needs to change the query generation it is pinned to. To update the user interface, for example, the managed object context that is associated with the main queue of the application needs to pin itself to the current query generation to make sure the user sees an accurate picture of what is stored in the persistent store. How does this work?
Saving
The managed object context pins itself to the current query generation when it has pushed its changes to the persistent store coordinator.
Merging
The managed object context is also updated when it merges in the changes of another managed object context.
Resetting
When a managed object context is reset, it is automatically pinned to the current query generation.
Explicit Change
As we discussed earlier, it is also possible for a managed object context to pin itself to another query generation by explicitly switching to a different query generation.
managedObjectContext.setQueryGenerationFrom(queryGenerationToken)
But note that the managed objects that are already registered with the managed object context need to be refreshed. It is up to the developer to take care of this. They are not automatically updated when the query generation of the managed object context changes. Updating the registered managed objects is as easy as invoking refreshAllObjects()
on the managed object context.
You can obtain the current query generation token by invoking the current()
class method of the NSQueryGenerationToken
class.
Should You Use Query Generations
We have only scratched the surface of query generations in this article. Even though query generations are a more advanced feature of Core Data, they solve a common problem. If you feel query generations are too complicated or you are not ready to use them in your project, I suggest you stick with the default behavior, which is using unpinned managed object contexts.
Also note that you can only use query generations if the persistent store of your application is a SQLite database in WAL mode. This is the most common setup when using Core Data in a project, which means you probably don't need to make any changes to start benefiting from query generations.