Skip to content
John Estropia edited this page Jul 26, 2015 · 3 revisions

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.

Incremental migrations

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.

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(...):

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 MigrationTypes, 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.

Contents