Updated Migrations (markdown)

John Estropia
2015-07-26 17:19:49 +09:00
parent 185f35af6e
commit fef861a615

@@ -1,7 +1,103 @@
(README pending)
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:
```swift
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*:
```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.
### Incremental migrations
(README pending)
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:
```swift
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:
```swift
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):
```swift
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.
### Forecasting migrations
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(...)`:
```swift
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:
```swift
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.
## Contents