Guarantee that a context won’t see store changes until you tell it to look.
Framework
- Core Data
Overview
Query generations give your UI a stable view of data in the database, regardless of changes happening to the store underneath. Whenever you read from a context, you see the same generation, or snapshot, of data until you choose to advance it to a later generation.
Use query generations when you want to isolate your view context from any changes made in the store by background threads in your app, app extensions, CloudKit, or other sources.
Ensure the Correct Type and Mode for the Persistent Store
To use query generations, the persistent store must be an NSSQLite
in write-ahead logging (WAL) journal mode. Core Data creates SQLite stores with WAL mode enabled by default.
Query generations leverage WAL mode to let you query against the historical state of the database. Core Data appends transactions to a .sqllite-wal
file, or journal, in the same directory as the main store file. When your context reads from the journal, it starts at the transaction associated with a specific generation, instead of at the most recent transaction.
To confirm whether a custom store has WAL mode enabled, turn on SQL logging. Choose Product > Scheme > Edit Scheme, choose the Run action, and add the following line under Arguments Passed on Launch.
-com.apple.CoreData.SQLDebug 1
Run your app, and look for the following output in the console.
CoreData: sql: pragma journal_mode=wal
If you try to use query generations with a store that’s not an NSSQLite
in WAL journal mode, your contexts gracefully revert to unpinned behavior.
Pin Your View to a Store Generation
By default, contexts are unpinned, and read from the store at the generation of the most recent transaction. Pinned contexts read from the store at the generation of a specific transaction.
To pin a context, call set
, passing an opaque NSQuery
. The context updates to the specified generation lazily on the next read (fetching or faulting) operation.
Use the current
generation token to pin the context to the generation corresponding to the most recent store transaction. For example, pass the current
generation token when setting up your stack to pin the view context to the first generation that it fetches.
try? persistentContainer.viewContext.setQueryGenerationFrom(.current)
Alternatively, use the query
from another pinned context to align both contexts to the same generation.
To unpin a context, call set
, passing nil
.
try? persistentContainer.viewContext.setQueryGenerationFrom(nil)
Nested contexts inherit their parent’s generation. They’re implicitly unpinned, but they see data as viewed through the generation of their parent with the addition of their parent’s pending changes.
A generation doesn’t include stores added to the store coordinator after the generation’s creation. Additionally, if you remove a store from the coordinator, do not try to load data from the deleted store into a context.
Update Your View Context to the Current Store Generation
Advance a context to the generation of the most recent transaction, and pin it there, by calling set
and passing the current
token. The context updates to the specified generation lazily on the next read (fetching or faulting) operation.
try? persistentContainer.viewContext.setQueryGenerationFrom(.current)
Alternatively, update a context’s generation by calling any of the following.
Update contexts to the current
generation as soon as a specific generation is no longer needed. Query generations hold a file lock open to maintain the integrity of the journal for the duration of a query generation. Once no contexts refer to a query generation, it expires, and the system can reclaim the journal disk space.
Refresh Objects
Refresh any managed objects registered to the context after you change the context’s query generation or unpin the context. Managed objects do not automatically refresh, as this behavior may not be desirable and is difficult to revert.
Call refresh
on the context to refresh its existing managed objects.
persistentContainer.viewContext.refreshAllObjects()
Call fetch(_:)
on the context to retrieve a fresh set of managed objects matching your request criteria.
let request: NSFetchRequest<Quake> = NSFetchRequest(entityName: "Quake")
request.fetchBatchSize = 10
try? persistentContainer.viewContext.fetch(request)
The fetch reads the journal from the context’s query generation if pinned, or from the most recent transaction if unpinned.