-
-
Notifications
You must be signed in to change notification settings - Fork 255
Migrations
So far we have only seen addSQLiteStoreAndWait(...)
used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method will block, even if a migration occurs. If migrations are expected, the asynchronous variant addSQLiteStore(... completion:)
method is recommended:
do {
let progress: NSProgress = try dataStack.addSQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2",
completion: { (result) -> Void in
switch result {
case .Success(let persistentStore):
print("Successfully added sqlite store: \(persistentStore)"
case .Failure(let error):
print("Failed adding sqlite store with error: \(error)"
}
}
)
}
catch {
print("Failed adding sqlite store with error: \(error as NSError)"
}
The completion
block reports a PersistentStoreResult
that indicates success or failure.
addSQLiteStore(...)
throws an error if the store at the specified URL conflicts with an existing store in the DataStack
, or if an existing sqlite file could not be read. If an error is thrown, the completion
block will not be executed.
Notice that this method also returns an optional NSProgress
. If nil
, no migrations are needed, thus progress reporting is unnecessary as well. If not nil
, you can use this to track migration progress by using standard KVO on the "fractionCompleted" key, or by using a closure-based utility exposed in NSProgress+Convenience.swift:
progress?.setProgressHandler { [weak self] (progress) -> Void in
self?.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
self?.percentLabel?.text = progress.localizedDescription // "50% completed"
self?.stepLabel?.text = progress.localizedAdditionalDescription // "0 of 2"
}
This closure is executed on the main thread so UIKit calls can be done safely.
By default, CoreStore uses Core Data's default automatic migration mechanism. In other words, CoreStore will try to migrate the existing persistent store to the .xcdatamodeld file's current model version. If no mapping model is found from the store's version to the data model's version, CoreStore gives up and reports an error.
The DataStack
lets you specify hints on how to break a migration into several sub-migrations using a MigrationChain
. This is typically passed to the DataStack
initializer and will be applied to all stores added to the DataStack
with addSQLiteStore(...)
and its variants:
let dataStack = DataStack(migrationChain:
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
The most common usage is to pass in the .xcdatamodeld version names in increasing order as above.
For more complex migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions:
let dataStack = DataStack(migrationChain: [
"MyAppModel": "MyAppModelV3",
"MyAppModelV2": "MyAppModelV4",
"MyAppModelV3": "MyAppModelV4"
])
This allows for different migration paths depending on the starting version. The example above resolves to the following paths:
- MyAppModel-MyAppModelV3-MyAppModelV4
- MyAppModelV2-MyAppModelV4
- MyAppModelV3-MyAppModelV4
Initializing with empty values (either nil
, []
, or [:]
) instructs the DataStack
to disable incremental migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version):
let dataStack = DataStack(migrationChain: nil)
The MigrationChain
is validated when passed to the DataStack
and unless it is empty, will raise an assertion if any of the following conditions are met:
- a version appears twice in an array
- a version appears twice as a key in a dictionary literal
- a loop is found in any of the paths
One important thing to remember is that if a MigrationChain
is specified, the .xcdatamodeld's "Current Version" will be bypassed and the MigrationChain
's leafmost version will be the DataStack
's base model version.
Sometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a requiredMigrationsForSQLiteStore(...)
method you can use to inspect a persistent store before you actually call addSQLiteStore(...)
:
do {
let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: "MyStore.sqlite")
if migrationTypes.count > 1
|| (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
// ... Show special waiting screen
}
else if migrationTypes.count > 0 {
// ... Show simple activity indicator
}
else {
// ... Do nothing
}
CoreStore.addSQLiteStore(/* ... */)
}
catch {
// ...
}
requiredMigrationsForSQLiteStore(...)
returns an array of MigrationType
s, where each item in the array may be either of the following values:
case Lightweight(sourceVersion: String, destinationVersion: String)
case Heavyweight(sourceVersion: String, destinationVersion: String)
Each MigrationType
indicates the migration type for each step in the MigrationChain
. Use these information as fit for your app.