Skip to content

Commit

Permalink
Migration (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
alemar11 authored Sep 13, 2018
1 parent a845ef0 commit 8888e77
Show file tree
Hide file tree
Showing 105 changed files with 3,427 additions and 365 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### 1.1.0
- Added migration between model versions.
- Refinements.

### 1.0.0

- Refinements.
Expand Down
207 changes: 181 additions & 26 deletions CoreDataPlus.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ platform :ios, '10.0'
use_frameworks!

target '<Your Target Name>' do
pod 'CoreDataPlus', '~> 1.0.0'
pod 'CoreDataPlus', '~> 1.1.0'
end
```

Expand All @@ -77,7 +77,7 @@ $ brew install carthage
To integrate CoreDataPlus into your Xcode project using Carthage, specify it in your `Cartfile`:

```ogdl
github "tinrobots/CoreDataPlus" ~> 1.0.0
github "tinrobots/CoreDataPlus" ~> 1.1.0
```

Run `carthage update` to build the framework and drag the built `CoreDataPlus.framework` into your Xcode project.
Expand All @@ -89,7 +89,7 @@ Once you have your Swift package set up, adding CoreDataPlus as a dependency is

```swift
dependencies: [
.package(url: "https://github.com/tinrobots/CoreDataPlus.git", from: "1.0.0")
.package(url: "https://github.com/tinrobots/CoreDataPlus.git", from: "1.1.0")
]
```

Expand Down
10 changes: 7 additions & 3 deletions Sources/CoreDataPlusError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ public enum CoreDataPlusError: Error {
case fetchFailed(error: Error)
case persistentStoreCoordinatorNotFound(context: NSManagedObjectContext)
case saveFailed(error: Error)
case migrationFailed(error: Error)

/// **CoreDataPlus**
///
/// The `Error` returned by a system framework associated with a CoreDataPlus failure error.
public var underlyingError: Error? {
switch self {
case .fetchFailed(let error), .saveFailed(let error):
case .executionFailed(let error), .fetchFailed(let error), .saveFailed(let error), .migrationFailed(error: let error):
return error
default:
return nil
Expand All @@ -72,13 +73,16 @@ extension CoreDataPlusError: LocalizedError {
return "Returned multiple objects, expected max 1."

case .fetchFailed(let error):
return "The fetch could not be completed because of error:\n\(error.localizedDescription)"
return "The fetch could not be completed because of error:\n\(error)"

case .persistentStoreCoordinatorNotFound(let context):
return "\(context.description) doesn't have a NSPersistentStoreCoordinator."

case .saveFailed(let error):
return "The save operation could not be completed because of error:\n\(error.localizedDescription)"
return "The save operation could not be completed because of error:\n\(error)"

case .migrationFailed(error: let error):
return "The migration could not be completed because of error:\n\(error)"
}
}

Expand Down
121 changes: 121 additions & 0 deletions Sources/Migration/Migration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// CoreDataPlus
//
// Copyright © 2016-2018 Tinrobots.
//
// 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.
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html
// https://developer.apple.com/documentation/coredata/heavyweight_migration
// https://www.objc.io/issues/4-core-data/core-data-migration/

import CoreData

public struct Migration {

private init() { }

/// **CoreDataPlus**
///
/// Migrates a store to a given version.
///
/// - Parameters:
/// - sourceURL: the current store URL.
/// - targetVersion: the ModelVersion to which the store is needed to migrate to.
/// - progress: a Progress instance to monitor the migration.
/// - Throws: It throws an error in cases of failure.
public static func migrateStore<Version: ModelVersion>(at sourceURL: URL, targetVersion: Version, progress: Progress? = nil) throws {
try migrateStore(from: sourceURL, to: sourceURL, targetVersion: targetVersion, deleteSource: false, progress: progress)
}

/// **CoreDataPlus**
///
/// Migrates a store to a given version.
///
/// - Parameters:
/// - sourceURL: the current store URL.
/// - targetURL: the store URL after the migration phase.
/// - targetVersion: the ModelVersion to which the store is needed to migrate to.
/// - deleteSource: if `true` the initial store will be deleted after the migration phase.
/// - progress: a Progress instance to monitor the migration.
/// - Throws: It throws an error in cases of failure.
public static func migrateStore<Version: ModelVersion>(from sourceURL: URL, to targetURL: URL, targetVersion: Version, deleteSource: Bool = false, progress: Progress? = nil) throws {
guard let sourceVersion = Version(persistentStoreURL: sourceURL as URL) else {
fatalError("A ModelVersion for the store at URL \(sourceURL) could not be found.")
}

do {
guard try sourceVersion.isMigrationPossible(for: sourceURL, to: targetVersion) else {
return
}

var currentURL = sourceURL
let steps = sourceVersion.migrationSteps(to: targetVersion)

guard steps.count > 0 else {
return
}

var migrationProgress: Progress?

if let progress = progress {
migrationProgress = Progress(totalUnitCount: Int64(steps.count), parent: progress, pendingUnitCount: progress.totalUnitCount)
}

for step in steps {
try autoreleasepool {
migrationProgress?.becomeCurrent(withPendingUnitCount: 1)
let manager = NSMigrationManager(sourceModel: step.sourceModel, destinationModel: step.destinationModel)
migrationProgress?.resignCurrent()

let destinationURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString)

for mapping in step.mappings {
try manager.migrateStore(from: currentURL,
sourceType: NSSQLiteStoreType,
options: nil,
with: mapping,
toDestinationURL: destinationURL,
destinationType: NSSQLiteStoreType,
destinationOptions: nil)
}

if currentURL != sourceURL {
try NSPersistentStoreCoordinator.destroyStore(at: currentURL)
}
currentURL = destinationURL
}
}

try NSPersistentStoreCoordinator.replaceStore(at: targetURL, withStoreAt: currentURL)

if currentURL != sourceURL {
try NSPersistentStoreCoordinator.destroyStore(at: currentURL)
}

if targetURL != sourceURL && deleteSource {
try NSPersistentStoreCoordinator.destroyStore(at: sourceURL)
}

} catch {
throw CoreDataPlusError.migrationFailed(error: error)
}

}

}
39 changes: 39 additions & 0 deletions Sources/Migration/MigrationSep.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// CoreDataPlus
//
// Copyright © 2016-2018 Tinrobots.
//
// 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 CoreData

/// **CoreDataPlus**
///
/// Represents a single step during the migration process.
public final class MigrationStep {
var sourceModel: NSManagedObjectModel
var destinationModel: NSManagedObjectModel
var mappings: [NSMappingModel]

init(source: NSManagedObjectModel, destination: NSManagedObjectModel, mappings: [NSMappingModel]) {
self.sourceModel = source
self.destinationModel = destination
self.mappings = mappings
}
}
Loading

0 comments on commit 8888e77

Please sign in to comment.