From 0c8a43c3b01a101c4278d5384315f91fa76ab55f Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 26 Jul 2015 17:04:19 +0900 Subject: [PATCH] added shorthand vars for inspecting MigrationType values. updated readme --- CoreStore.xcodeproj/project.pbxproj | 4 + CoreStore/Migrating/MigrationResult.swift | 77 +---------- CoreStore/Migrating/MigrationType.swift | 125 ++++++++++++++++++ .../MyAppModel.xcdatamodeld/.xccurrentversion | 5 + .../MyAppModel.xcdatamodel/contents | 4 + README.md | 66 ++++++++- 6 files changed, 204 insertions(+), 77 deletions(-) create mode 100644 CoreStore/Migrating/MigrationType.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/.xccurrentversion create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/MyAppModel.xcdatamodel/contents diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 223578f..56bda4c 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */; }; B56965241B356B820075EE4A /* MigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965231B356B820075EE4A /* MigrationResult.swift */; }; B59D5C221B5BA34B00453479 /* NSFileManager+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */; }; + B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A261201B64BFDB006EB6D3 /* MigrationType.swift */; }; B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; }; B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; }; @@ -116,6 +117,7 @@ B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Migration.swift"; sourceTree = ""; }; B56965231B356B820075EE4A /* MigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationResult.swift; sourceTree = ""; }; B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFileManager+Setup.swift"; sourceTree = ""; }; + B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = ""; }; B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = ""; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = ""; }; @@ -276,6 +278,7 @@ B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */, B5FAD6AD1B518DCB00714891 /* CoreStore+Migration.swift */, B56007151B4018AB00A9A8F9 /* MigrationChain.swift */, + B5A261201B64BFDB006EB6D3 /* MigrationType.swift */, B56965231B356B820075EE4A /* MigrationResult.swift */, ); path = Migrating; @@ -552,6 +555,7 @@ B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */, B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */, B5E84F131AFF847B0064E85B /* Where.swift in Sources */, + B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */, B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */, B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */, B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */, diff --git a/CoreStore/Migrating/MigrationResult.swift b/CoreStore/Migrating/MigrationResult.swift index a72f2e3..da635d9 100644 --- a/CoreStore/Migrating/MigrationResult.swift +++ b/CoreStore/Migrating/MigrationResult.swift @@ -2,7 +2,7 @@ // MigrationResult.swift // CoreStore // -// Copyright (c) 2014 John Rommel Estropia +// Copyright (c) 2015 John Rommel Estropia // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -26,81 +26,6 @@ import Foundation -// MARK: - MigrationType - -/** -The `MigrationType` specifies the type of migration required for a store. -*/ -public enum MigrationType: BooleanType { - - // MARK: Public - - /** - Indicates that the persistent store matches the latest model version and no migration is needed - */ - case None(version: String) - - /** - Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed - */ - case Lightweight(sourceVersion: String, destinationVersion: String) - - /** - Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed - */ - case Heavyweight(sourceVersion: String, destinationVersion: String) - - /** - Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`. - */ - public var sourceVersion: String { - - switch self { - - case .None(let version): - return version - - case .Lightweight(let sourceVersion, _): - return sourceVersion - - case .Heavyweight(let sourceVersion, _): - return sourceVersion - } - } - - /** - Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`. - */ - public var destinationVersion: String { - - switch self { - - case .None(let version): - return version - - case .Lightweight(_, let destinationVersion): - return destinationVersion - - case .Heavyweight(_, let destinationVersion): - return destinationVersion - } - } - - - // MARK: BooleanType - - public var boolValue: Bool { - - switch self { - - case .None: return false - case .Lightweight: return true - case .Heavyweight: return true - } - } -} - - // MARK: - MigrationResult /** diff --git a/CoreStore/Migrating/MigrationType.swift b/CoreStore/Migrating/MigrationType.swift new file mode 100644 index 0000000..21f4865 --- /dev/null +++ b/CoreStore/Migrating/MigrationType.swift @@ -0,0 +1,125 @@ +// +// MigrationType.swift +// CoreStore +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +// MARK: - MigrationType + +/** +The `MigrationType` specifies the type of migration required for a store. +*/ +public enum MigrationType: BooleanType { + + // MARK: Public + + /** + Indicates that the persistent store matches the latest model version and no migration is needed + */ + case None(version: String) + + /** + Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed + */ + case Lightweight(sourceVersion: String, destinationVersion: String) + + /** + Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed + */ + case Heavyweight(sourceVersion: String, destinationVersion: String) + + /** + Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`. + */ + public var sourceVersion: String { + + switch self { + + case .None(let version): + return version + + case .Lightweight(let sourceVersion, _): + return sourceVersion + + case .Heavyweight(let sourceVersion, _): + return sourceVersion + } + } + + /** + Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`. + */ + public var destinationVersion: String { + + switch self { + + case .None(let version): + return version + + case .Lightweight(_, let destinationVersion): + return destinationVersion + + case .Heavyweight(_, let destinationVersion): + return destinationVersion + } + } + + /** + Returns `true` if the `MigrationType` is a lightweight migration. Used as syntactic sugar. + */ + public var isLightweightMigration: Bool { + + if case .Lightweight = self { + + return true + } + return false + } + + /** + Returns `true` if the `MigrationType` is a heavyweight migration. Used as syntactic sugar. + */ + public var isHeavyweightMigration: Bool { + + if case .Heavyweight = self { + + return true + } + return false + } + + + // MARK: BooleanType + + public var boolValue: Bool { + + switch self { + + case .None: return false + case .Lightweight: return true + case .Heavyweight: return true + } + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/.xccurrentversion b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/.xccurrentversion @@ -0,0 +1,5 @@ + + + + + diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/MyAppModel.xcdatamodel/contents b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/MyAppModel.xcdatamodel/contents new file mode 100644 index 0000000..34afc82 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MyAppModel.xcdatamodeld/MyAppModel.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 22f8d2f..f11ee1c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Unleashing the real power of Core Data with the elegance and safety of Swift - [Setting up](#setting-up) - [Migrations](#migrations) - [Incremental migrations](#incremental-migrations) + - [Forecasting migrations](#forecasting-migrations) - [Saving and processing transactions](#saving-and-processing-transactions) - [Transaction types](#transaction-types) - [Asynchronous transactions](#asynchronous-transactions) @@ -287,7 +288,70 @@ 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.