mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-22 01:19:16 +01:00
WIP: allow migrations for CoreStoreObjects
This commit is contained in:
@@ -222,7 +222,7 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
|
||||
|
||||
internal init(_ error: Error?) {
|
||||
|
||||
self = error.flatMap { $0.bridgeToSwift } ?? .unknown
|
||||
self = error.flatMap({ $0.bridgeToSwift }) ?? .unknown
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - XcdatamodelFilename
|
||||
// MARK: - XcodeDataModelFileName
|
||||
|
||||
/**
|
||||
A `String` that pertains to the name of an *.xcdatamodeld file (without the file extension).
|
||||
*/
|
||||
public typealias XcdatamodelFilename = String
|
||||
public typealias XcodeDataModelFileName = String
|
||||
|
||||
|
||||
// MARK: - ModelConfiguration
|
||||
|
||||
@@ -580,11 +580,11 @@ extension UUID: ImportableAttributeType {
|
||||
|
||||
enum Static {
|
||||
|
||||
static let empty: UUID = {
|
||||
static let empty: UUID = cs_lazy {
|
||||
|
||||
var zero = Array<UInt8>(repeating: 0, count: 16)
|
||||
return NSUUID(uuidBytes: &zero) as UUID
|
||||
}()
|
||||
}
|
||||
}
|
||||
return Static.empty
|
||||
}
|
||||
|
||||
106
Sources/Internal/EntityIdentifier.swift
Normal file
106
Sources/Internal/EntityIdentifier.swift
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// EntityIdentifier.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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 CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - EntityIdentifier
|
||||
|
||||
internal struct EntityIdentifier: Hashable {
|
||||
|
||||
// MARK: - Category
|
||||
|
||||
internal enum Category: Int {
|
||||
|
||||
case coreData
|
||||
case coreStore
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
|
||||
internal let category: Category
|
||||
internal let interfacedClassName: String
|
||||
|
||||
internal init(_ type: NSManagedObject.Type) {
|
||||
|
||||
self.category = .coreData
|
||||
self.interfacedClassName = String(reflecting: type)
|
||||
}
|
||||
|
||||
internal init(_ type: CoreStoreObject.Type) {
|
||||
|
||||
self.category = .coreStore
|
||||
self.interfacedClassName = String(reflecting: type)
|
||||
}
|
||||
|
||||
internal init(_ type: DynamicObject.Type) {
|
||||
|
||||
switch type {
|
||||
|
||||
case let type as NSManagedObject.Type:
|
||||
self.init(type)
|
||||
|
||||
case let type as CoreStoreObject.Type:
|
||||
self.init(type)
|
||||
|
||||
default:
|
||||
CoreStore.abort("\(cs_typeName(DynamicObject.self)) is not meant to be implemented by external types.")
|
||||
}
|
||||
}
|
||||
|
||||
internal init(_ entityDescription: NSEntityDescription) {
|
||||
|
||||
if let anyEntity = entityDescription.anyEntity {
|
||||
|
||||
self.category = .coreStore
|
||||
self.interfacedClassName = NSStringFromClass(anyEntity.type)
|
||||
}
|
||||
else {
|
||||
|
||||
self.category = .coreData
|
||||
self.interfacedClassName = entityDescription.managedObjectClassName!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
static func == (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool {
|
||||
|
||||
return lhs.category == rhs.category
|
||||
&& lhs.interfacedClassName == rhs.interfacedClassName
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
var hashValue: Int {
|
||||
|
||||
return self.category.hashValue
|
||||
^ self.interfacedClassName.hashValue
|
||||
}
|
||||
}
|
||||
@@ -100,3 +100,12 @@ internal func cs_typeName(_ name: String?) -> String {
|
||||
|
||||
return "<\(name ?? "unknown")>"
|
||||
}
|
||||
|
||||
|
||||
// MARK: Functional
|
||||
|
||||
@inline(__always)
|
||||
internal func cs_lazy<T>(_ closure: () -> T) -> T {
|
||||
|
||||
return closure()
|
||||
}
|
||||
|
||||
76
Sources/Internal/NSEntityDescription+DynamicModel.swift
Normal file
76
Sources/Internal/NSEntityDescription+DynamicModel.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// NSEntityDescription+DynamicModel.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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 CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - NSEntityDescription
|
||||
|
||||
internal extension NSEntityDescription {
|
||||
|
||||
@nonobjc
|
||||
internal var anyEntity: CoreStoreSchema.AnyEntity? {
|
||||
|
||||
get {
|
||||
|
||||
guard let userInfo = self.userInfo,
|
||||
let typeName = userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] as! String?,
|
||||
let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return CoreStoreSchema.AnyEntity(
|
||||
type: NSClassFromString(typeName) as! CoreStoreObject.Type,
|
||||
entityName: entityName
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
if let newValue = newValue {
|
||||
|
||||
self.userInfo = [
|
||||
UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type),
|
||||
UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName
|
||||
]
|
||||
}
|
||||
else {
|
||||
|
||||
self.userInfo = [:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: - UserInfoKey
|
||||
|
||||
fileprivate enum UserInfoKey {
|
||||
|
||||
fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName"
|
||||
fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName"
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectModel+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectModel
|
||||
|
||||
internal extension NSManagedObjectModel {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
internal static func fromBundle(_ bundle: Bundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
|
||||
|
||||
guard let modelFilePath = bundle.path(forResource: modelName, ofType: "momd") else {
|
||||
|
||||
// For users migrating from very old Xcode versions: Old xcdatamodel files are not contained inside xcdatamodeld (with a "d"), and will thus fail this check. If that was the case, create a new xcdatamodeld file and copy all contents into the new model.
|
||||
let foundModels = bundle
|
||||
.paths(forResourcesOfType: "momd", inDirectory: nil)
|
||||
.map({ ($0 as NSString).lastPathComponent })
|
||||
CoreStore.abort("Could not find \"\(modelName).momd\" from the bundle \"\(bundle.bundleIdentifier ?? "<nil>")\". Other model files in bundle: \(foundModels.coreStoreDumpString)")
|
||||
}
|
||||
|
||||
let modelFileURL = URL(fileURLWithPath: modelFilePath)
|
||||
let versionInfoPlistURL = modelFileURL.appendingPathComponent("VersionInfo.plist", isDirectory: false)
|
||||
|
||||
guard let versionInfo = NSDictionary(contentsOf: versionInfoPlistURL),
|
||||
let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else {
|
||||
|
||||
CoreStore.abort("Could not load \(cs_typeName(NSManagedObjectModel.self)) metadata from path \"\(versionInfoPlistURL)\".")
|
||||
}
|
||||
|
||||
let modelVersions = Set(versionHashes.keys)
|
||||
let currentModelVersion: String
|
||||
if let plistModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String,
|
||||
modelVersionHints.isEmpty || modelVersionHints.contains(plistModelVersion) {
|
||||
|
||||
currentModelVersion = plistModelVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.intersection(modelVersionHints).first {
|
||||
|
||||
CoreStore.log(
|
||||
.warning,
|
||||
message: "The MigrationChain leaf versions do not include the model file's current version. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
|
||||
|
||||
if !modelVersionHints.isEmpty {
|
||||
|
||||
CoreStore.log(
|
||||
.warning,
|
||||
message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
}
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.abort("No model files were found in URL \"\(modelFileURL)\".")
|
||||
}
|
||||
|
||||
var modelVersionFileURL: URL?
|
||||
for modelVersion in modelVersions {
|
||||
|
||||
let fileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
|
||||
if modelVersion == currentModelVersion {
|
||||
|
||||
modelVersionFileURL = fileURL
|
||||
continue
|
||||
}
|
||||
|
||||
precondition(
|
||||
NSManagedObjectModel(contentsOf: fileURL) != nil,
|
||||
"Could not find the \"\(modelVersion).mom\" version file for the model at URL \"\(modelFileURL)\"."
|
||||
)
|
||||
}
|
||||
|
||||
if let modelVersionFileURL = modelVersionFileURL,
|
||||
let rootModel = NSManagedObjectModel(contentsOf: modelVersionFileURL) {
|
||||
|
||||
// TODO: apply to DynamicModel as well
|
||||
rootModel.modelVersionFileURL = modelVersionFileURL
|
||||
rootModel.modelVersions = modelVersions
|
||||
rootModel.currentModelVersion = currentModelVersion
|
||||
return rootModel
|
||||
}
|
||||
|
||||
CoreStore.abort("Could not create an \(cs_typeName(NSManagedObjectModel.self)) from the model at URL \"\(modelFileURL)\".")
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal private(set) var currentModelVersion: String? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSString? = cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.currentModelVersion,
|
||||
inObject: self
|
||||
)
|
||||
return value as String?
|
||||
}
|
||||
set {
|
||||
|
||||
cs_setAssociatedCopiedObject(
|
||||
newValue == nil ? nil : (newValue! as NSString),
|
||||
forKey: &PropertyKeys.currentModelVersion,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal private(set) var modelVersions: Set<String>? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSSet? = cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.modelVersions,
|
||||
inObject: self
|
||||
)
|
||||
return value as? Set<String>
|
||||
}
|
||||
set {
|
||||
|
||||
cs_setAssociatedCopiedObject(
|
||||
newValue == nil ? nil : (newValue! as NSSet),
|
||||
forKey: &PropertyKeys.modelVersions,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal var entityDescriptionsByEntityIdentifier: [EntityIdentifier: NSEntityDescription] {
|
||||
|
||||
if let mapping: NSDictionary = cs_getAssociatedObjectForKey(&PropertyKeys.objectClassNamesByEntityName, inObject: self) {
|
||||
|
||||
return mapping as! [EntityIdentifier: NSEntityDescription]
|
||||
}
|
||||
|
||||
var mapping: [EntityIdentifier: NSEntityDescription] = [:]
|
||||
self.entities.forEach { (entityDescription) in
|
||||
|
||||
let entityIdentifier = EntityIdentifier(entityDescription)
|
||||
mapping[entityIdentifier] = entityDescription
|
||||
}
|
||||
cs_setAssociatedCopiedObject(
|
||||
mapping as NSDictionary,
|
||||
forKey: &PropertyKeys.objectClassNamesByEntityName,
|
||||
inObject: self
|
||||
)
|
||||
return mapping
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func mergedModels() -> [NSManagedObjectModel] {
|
||||
|
||||
return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self]
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal subscript(modelVersion: String) -> NSManagedObjectModel? {
|
||||
|
||||
if modelVersion == self.currentModelVersion {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
guard let modelFileURL = self.modelFileURL,
|
||||
let modelVersions = self.modelVersions,
|
||||
modelVersions.contains(modelVersion) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: apply to DynamicModel as well
|
||||
let versionModelFileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
guard let model = NSManagedObjectModel(contentsOf: versionModelFileURL) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
model.currentModelVersion = modelVersion
|
||||
model.modelVersionFileURL = versionModelFileURL
|
||||
model.modelVersions = modelVersions
|
||||
return model
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal subscript(metadata: [String: Any]) -> NSManagedObjectModel? {
|
||||
|
||||
guard let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : Data] else {
|
||||
|
||||
return nil
|
||||
}
|
||||
for modelVersion in self.modelVersions ?? [] {
|
||||
|
||||
if let versionModel = self[modelVersion], modelHashes == versionModel.entityVersionHashesByName {
|
||||
|
||||
return versionModel
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private var modelFileURL: URL? {
|
||||
|
||||
get {
|
||||
|
||||
return self.modelVersionFileURL?.deletingLastPathComponent()
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private var modelVersionFileURL: URL? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSURL? = cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.modelVersionFileURL,
|
||||
inObject: self
|
||||
)
|
||||
return value as URL?
|
||||
}
|
||||
set {
|
||||
|
||||
cs_setAssociatedCopiedObject(
|
||||
newValue as NSURL?,
|
||||
forKey: &PropertyKeys.modelVersionFileURL,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var objectClassNamesByEntityName: Void?
|
||||
|
||||
static var modelVersionFileURL: Void?
|
||||
static var modelVersions: Void?
|
||||
static var currentModelVersion: Void?
|
||||
}
|
||||
}
|
||||
@@ -158,6 +158,32 @@ extension CoreStoreError: CustomDebugStringConvertible, CoreStoreDebugStringConv
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreSchema
|
||||
|
||||
extension CoreStoreSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("modelVersion", self.modelVersion),
|
||||
("entitiesByConfiguration", self.entitiesByConfiguration),
|
||||
("rawModel", self.rawModel())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
extension DataStack: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
@@ -179,14 +205,38 @@ extension DataStack: CustomDebugStringConvertible, CoreStoreDebugStringConvertib
|
||||
("coordinator", self.coordinator),
|
||||
("rootSavingContext", self.rootSavingContext),
|
||||
("mainContext", self.mainContext),
|
||||
("model", self.model),
|
||||
("migrationChain", self.migrationChain),
|
||||
("schemaHistory", self.schemaHistory),
|
||||
("coordinator.persistentStores", self.coordinator.persistentStores)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Entity
|
||||
|
||||
extension Entity: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("type", self.type),
|
||||
("entityName", self.entityName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - From
|
||||
|
||||
extension From: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
@@ -350,6 +400,31 @@ extension LegacySQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringC
|
||||
}
|
||||
|
||||
|
||||
// MARK: - LegacyXcodeDataModel
|
||||
|
||||
extension LegacyXcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("modelVersion", self.modelVersion),
|
||||
("rawModel", self.rawModel())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListMonitor
|
||||
|
||||
@available(OSX 10.12, *)
|
||||
@@ -456,7 +531,7 @@ extension MigrationChain: CustomDebugStringConvertible, CoreStoreDebugStringConv
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
guard self.valid else {
|
||||
guard self.isValid else {
|
||||
|
||||
return "<invalid migration chain>"
|
||||
}
|
||||
@@ -669,6 +744,32 @@ extension SectionBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertib
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SchemaHistory
|
||||
|
||||
extension SchemaHistory: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("currentModelVersion", self.currentModelVersion),
|
||||
("migrationChain", self.migrationChain),
|
||||
("schemaByVersion", self.schemaByVersion)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Select
|
||||
|
||||
extension Select: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
@@ -896,6 +997,32 @@ extension Where: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - XcodeDataModel
|
||||
|
||||
extension XcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
return formattedDebugDescription(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreDebugStringConvertible
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("modelVersion", self.modelVersion),
|
||||
("modelVersionFileURL", self.modelVersionFileURL),
|
||||
("rawModel", self.rawModel())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private: Utilities
|
||||
|
||||
private typealias DumpInfo = [(key: String, value: Any)]
|
||||
@@ -972,7 +1099,7 @@ public protocol CoreStoreDebugStringConvertible {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private:
|
||||
// MARK: - Standard Types:
|
||||
|
||||
extension Array: CoreStoreDebugStringConvertible {
|
||||
|
||||
@@ -1214,7 +1341,15 @@ extension NSSortDescriptor: CoreStoreDebugStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
extension URL: CoreStoreDebugStringConvertible {
|
||||
extension NSString: CoreStoreDebugStringConvertible {
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return "\"\(self)\""
|
||||
}
|
||||
}
|
||||
|
||||
extension NSURL: CoreStoreDebugStringConvertible {
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
@@ -1249,3 +1384,11 @@ extension String: CoreStoreDebugStringConvertible {
|
||||
return "\"\(self)\""
|
||||
}
|
||||
}
|
||||
|
||||
extension URL: CoreStoreDebugStringConvertible {
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
return "\"\(self)\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public extension DataStack {
|
||||
|
||||
try storage.eraseStorageAndWait(
|
||||
metadata: metadata,
|
||||
soureModelHint: self.model[metadata]
|
||||
soureModelHint: self.schemaHistory.schema(for: metadata)?.rawModel()
|
||||
)
|
||||
_ = try self.addStorageAndWait(storage)
|
||||
|
||||
@@ -382,7 +382,9 @@ public extension DataStack {
|
||||
at: cacheFileURL,
|
||||
options: storeOptions
|
||||
)
|
||||
_ = try self.model[metadata].flatMap(storage.eraseStorageAndWait)
|
||||
_ = try self.schemaHistory
|
||||
.schema(for: metadata)
|
||||
.flatMap({ try storage.eraseStorageAndWait(soureModel: $0.rawModel()) })
|
||||
_ = try self.createPersistentStoreFromStorage(
|
||||
storage,
|
||||
finalURL: cacheFileURL,
|
||||
@@ -497,7 +499,7 @@ public extension DataStack {
|
||||
|
||||
let error = CoreStoreError.mappingModelNotFound(
|
||||
localStoreURL: fileURL,
|
||||
targetModel: self.model,
|
||||
targetModel: self.schemaHistory.rawModel,
|
||||
targetModelVersion: self.modelVersion
|
||||
)
|
||||
CoreStore.log(
|
||||
@@ -545,12 +547,12 @@ public extension DataStack {
|
||||
|
||||
let error = CoreStoreError.mappingModelNotFound(
|
||||
localStoreURL: storage.fileURL,
|
||||
targetModel: self.model,
|
||||
targetModel: self.schemaHistory.rawModel,
|
||||
targetModelVersion: self.modelVersion
|
||||
)
|
||||
CoreStore.log(
|
||||
error,
|
||||
"Failed to find migration steps from \(cs_typeName(storage)) at URL \"\(storage.fileURL)\" to version model \"\(self.model)\"."
|
||||
"Failed to find migration steps from \(cs_typeName(storage)) at URL \"\(storage.fileURL)\" to version model \"\(self.schemaHistory.rawModel)\"."
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@@ -592,7 +594,7 @@ public extension DataStack {
|
||||
let progress = Progress(parent: nil, userInfo: nil)
|
||||
progress.totalUnitCount = numberOfMigrations
|
||||
|
||||
for (sourceModel, destinationModel, mappingModel, _) in migrationSteps {
|
||||
for (sourceModel, destinationModel, mappingModel, migrationType) in migrationSteps {
|
||||
|
||||
progress.becomeCurrent(withPendingUnitCount: 1)
|
||||
|
||||
@@ -620,8 +622,13 @@ public extension DataStack {
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
migrationResult = MigrationResult(error)
|
||||
|
||||
let migrationError = CoreStoreError(error)
|
||||
CoreStore.log(
|
||||
migrationError,
|
||||
"Failed to migrate version model \"\(migrationType.sourceVersion)\" to version \"\(migrationType.destinationVersion)\"."
|
||||
)
|
||||
migrationResult = MigrationResult(migrationError)
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
@@ -658,28 +665,27 @@ public extension DataStack {
|
||||
|
||||
private func computeMigrationFromStorage<T: LocalStorage>(_ storage: T, metadata: [String: Any]) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
|
||||
|
||||
let model = self.model
|
||||
if model.isConfiguration(withName: storage.configuration, compatibleWithStoreMetadata: metadata) {
|
||||
let schemaHistory = self.schemaHistory
|
||||
if schemaHistory.rawModel.isConfiguration(withName: storage.configuration, compatibleWithStoreMetadata: metadata) {
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
guard let initialModel = model[metadata],
|
||||
var currentVersion = initialModel.currentModelVersion else {
|
||||
|
||||
return nil
|
||||
guard let initialSchema = schemaHistory.schema(for: metadata) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let migrationChain: MigrationChain = self.migrationChain.empty
|
||||
? [currentVersion: model.currentModelVersion!]
|
||||
: self.migrationChain
|
||||
var currentVersion = initialSchema.modelVersion
|
||||
let migrationChain: MigrationChain = schemaHistory.migrationChain.isEmpty
|
||||
? [currentVersion: schemaHistory.currentModelVersion]
|
||||
: schemaHistory.migrationChain
|
||||
|
||||
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
|
||||
|
||||
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
|
||||
let sourceModel = model[currentVersion],
|
||||
sourceModel != model,
|
||||
let destinationModel = model[nextVersion] {
|
||||
let sourceModel = schemaHistory.rawModel(for: currentVersion),
|
||||
sourceModel != schemaHistory.rawModel,
|
||||
let destinationModel = schemaHistory.rawModel(for: nextVersion) {
|
||||
|
||||
if let mappingModel = NSMappingModel(
|
||||
from: storage.mappingModelBundles,
|
||||
@@ -727,7 +733,7 @@ public extension DataStack {
|
||||
currentVersion = nextVersion
|
||||
}
|
||||
|
||||
if migrationSteps.last?.destinationModel == model {
|
||||
if migrationSteps.last?.destinationModel == schemaHistory.rawModel {
|
||||
|
||||
return migrationSteps
|
||||
}
|
||||
@@ -775,21 +781,8 @@ public extension DataStack {
|
||||
}
|
||||
catch {
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItem(at: temporaryFileURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
|
||||
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
|
||||
let migrationError = CoreStoreError(error)
|
||||
CoreStore.log(
|
||||
migrationError,
|
||||
"Failed to migrate from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
|
||||
)
|
||||
|
||||
throw migrationError
|
||||
_ = try? fileManager.removeItem(at: temporaryFileURL)
|
||||
throw CoreStoreError(error)
|
||||
}
|
||||
|
||||
do {
|
||||
@@ -806,21 +799,8 @@ public extension DataStack {
|
||||
}
|
||||
catch {
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItem(at: temporaryFileURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
|
||||
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
|
||||
let fileError = CoreStoreError(error)
|
||||
CoreStore.log(
|
||||
fileError,
|
||||
"Failed to save store after migrating from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
|
||||
)
|
||||
|
||||
throw fileError
|
||||
_ = try? fileManager.removeItem(at: temporaryFileURL)
|
||||
throw CoreStoreError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = []
|
||||
self.leafVersions = []
|
||||
self.valid = true
|
||||
self.isValid = true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,7 +81,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
self.isValid = true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,13 +93,13 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
|
||||
var lastVersion: String?
|
||||
var versionTree = [String: String]()
|
||||
var valid = true
|
||||
var isValid = true
|
||||
for version in elements {
|
||||
|
||||
if let lastVersion = lastVersion,
|
||||
let _ = versionTree.updateValue(version, forKey: lastVersion) {
|
||||
|
||||
valid = false
|
||||
isValid = false
|
||||
}
|
||||
lastVersion = version
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
self.versionTree = versionTree
|
||||
self.rootVersions = Set(elements.prefix(1))
|
||||
self.leafVersions = Set(elements.suffix(1))
|
||||
self.valid = valid
|
||||
self.isValid = isValid
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +115,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
*/
|
||||
public init(_ elements: [(String, String)]) {
|
||||
|
||||
var valid = true
|
||||
var isValid = true
|
||||
var versionTree = [String: String]()
|
||||
elements.forEach { (sourceVersion, destinationVersion) in
|
||||
|
||||
@@ -126,7 +126,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
|
||||
CoreStore.assert(false, "\(cs_typeName(MigrationChain.self))'s migration chain could not be created due to ambiguous version paths.")
|
||||
|
||||
valid = false
|
||||
isValid = false
|
||||
}
|
||||
let leafVersions = Set(
|
||||
elements
|
||||
@@ -156,7 +156,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
self.versionTree = versionTree
|
||||
self.rootVersions = Set(versionTree.keys).subtracting(versionTree.values)
|
||||
self.leafVersions = leafVersions
|
||||
self.valid = valid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0
|
||||
self.isValid = isValid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,7 +223,7 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
return lhs.versionTree == rhs.versionTree
|
||||
&& lhs.rootVersions == rhs.rootVersions
|
||||
&& lhs.leafVersions == rhs.leafVersions
|
||||
&& lhs.valid == rhs.valid
|
||||
&& lhs.isValid == rhs.isValid
|
||||
}
|
||||
|
||||
|
||||
@@ -231,9 +231,9 @@ public struct MigrationChain: ExpressibleByNilLiteral, ExpressibleByStringLitera
|
||||
|
||||
internal let rootVersions: Set<String>
|
||||
internal let leafVersions: Set<String>
|
||||
internal let valid: Bool
|
||||
internal let isValid: Bool
|
||||
|
||||
internal var empty: Bool {
|
||||
internal var isEmpty: Bool {
|
||||
|
||||
return self.versionTree.count <= 0
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
|
||||
- parameter versionChain: the version strings that indicate the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@objc
|
||||
public convenience init(modelName: XcdatamodelFilename?, bundle: Bundle?, versionChain: [String]?) {
|
||||
public convenience init(modelName: XcodeDataModelFileName?, bundle: Bundle?, versionChain: [String]?) {
|
||||
|
||||
self.init(
|
||||
DataStack(
|
||||
@@ -73,7 +73,7 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
|
||||
- parameter versionTree: the version strings that indicate the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@objc
|
||||
public convenience init(modelName: XcdatamodelFilename?, bundle: Bundle?, versionTree: [String: String]?) {
|
||||
public convenience init(modelName: XcodeDataModelFileName?, bundle: Bundle?, versionTree: [String: String]?) {
|
||||
|
||||
self.init(
|
||||
DataStack(
|
||||
@@ -84,40 +84,6 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter model: the `NSManagedObjectModel` for the stack
|
||||
- parameter versionChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@objc
|
||||
public convenience init(model: NSManagedObjectModel, versionChain: [String]?) {
|
||||
|
||||
self.init(
|
||||
DataStack(
|
||||
model: model,
|
||||
migrationChain: versionChain.flatMap { MigrationChain($0) } ?? nil
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter model: the `NSManagedObjectModel` for the stack
|
||||
- parameter versionTree: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@objc
|
||||
public convenience init(model: NSManagedObjectModel, versionTree: [String]?) {
|
||||
|
||||
self.init(
|
||||
DataStack(
|
||||
model: model,
|
||||
migrationChain: versionTree.flatMap { MigrationChain($0) } ?? nil
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the stack's model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
|
||||
*/
|
||||
@@ -261,6 +227,42 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
|
||||
|
||||
// MARK: Deprecated
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter model: the `NSManagedObjectModel` for the stack
|
||||
- parameter versionChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@available(*, deprecated: 3.1, message: "Use the -[initWithModelName:bundle:versionChain:] initializer.")
|
||||
@objc
|
||||
public convenience init(model: NSManagedObjectModel, versionChain: [String]?) {
|
||||
|
||||
self.init(
|
||||
DataStack(
|
||||
model: model,
|
||||
migrationChain: versionChain.flatMap { MigrationChain($0) } ?? nil
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter model: the `NSManagedObjectModel` for the stack
|
||||
- parameter versionTree: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@available(*, deprecated: 3.1, message: "Use the -[initWithModelName:bundle:versionTree:] initializer.")
|
||||
@objc
|
||||
public convenience init(model: NSManagedObjectModel, versionTree: [String]?) {
|
||||
|
||||
self.init(
|
||||
DataStack(
|
||||
model: model,
|
||||
migrationChain: versionTree.flatMap { MigrationChain($0) } ?? nil
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the stack's model.
|
||||
*/
|
||||
|
||||
@@ -41,30 +41,24 @@ public final class DataStack: Equatable {
|
||||
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
|
||||
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
public convenience init(modelName: XcdatamodelFilename = DataStack.applicationName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
|
||||
public convenience init(modelName: XcodeDataModelFileName = DataStack.applicationName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
|
||||
|
||||
let model = NSManagedObjectModel.fromBundle(
|
||||
bundle,
|
||||
modelName: modelName,
|
||||
modelVersionHints: migrationChain.leafVersions
|
||||
)
|
||||
self.init(model: model, migrationChain: migrationChain)
|
||||
}
|
||||
|
||||
public convenience init(dynamicModel: DynamicModel) {
|
||||
|
||||
self.init(model: dynamicModel.createModel())
|
||||
}
|
||||
|
||||
public convenience init(dynamicModels: [DynamicModel], migrationChain: MigrationChain = nil) {
|
||||
|
||||
CoreStore.assert(
|
||||
migrationChain.valid,
|
||||
"Invalid migration chain passed to the \(cs_typeName(DataStack.self)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
|
||||
)
|
||||
self.init(
|
||||
model: NSManagedObjectModel(byMerging: dynamicModels.map({ $0.createModel() }))!,
|
||||
migrationChain: migrationChain
|
||||
schemaHistory: SchemaHistory(
|
||||
modelName: modelName,
|
||||
bundle: bundle,
|
||||
migrationChain: migrationChain
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
public convenience init(_ schema: DynamicSchema, _ otherSchema: DynamicSchema..., migrationChain: MigrationChain = nil) {
|
||||
|
||||
self.init(
|
||||
schemaHistory: SchemaHistory(
|
||||
allSchema: [schema] + otherSchema,
|
||||
migrationChain: migrationChain
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,21 +68,20 @@ public final class DataStack: Equatable {
|
||||
- parameter model: the `NSManagedObjectModel` for the stack
|
||||
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
public required init(model: NSManagedObjectModel, migrationChain: MigrationChain = nil) {
|
||||
public required init(schemaHistory: SchemaHistory) {
|
||||
|
||||
// TODO: test before release (rolled back)
|
||||
// _ = DataStack.isGloballyInitialized
|
||||
|
||||
CoreStore.assert(
|
||||
migrationChain.valid,
|
||||
schemaHistory.migrationChain.isValid,
|
||||
"Invalid migration chain passed to the \(cs_typeName(DataStack.self)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
|
||||
)
|
||||
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: schemaHistory.rawModel)
|
||||
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
|
||||
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
|
||||
self.model = model
|
||||
self.migrationChain = migrationChain
|
||||
self.schemaHistory = schemaHistory
|
||||
|
||||
self.rootSavingContext.parentStack = self
|
||||
|
||||
@@ -100,7 +93,7 @@ public final class DataStack: Equatable {
|
||||
*/
|
||||
public var modelVersion: String {
|
||||
|
||||
return self.model.currentModelVersion!
|
||||
return self.schemaHistory.currentModelVersion
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +102,7 @@ public final class DataStack: Equatable {
|
||||
public func entityTypesByName(for type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
|
||||
|
||||
var entityTypesByName: [EntityName: NSManagedObject.Type] = [:]
|
||||
for (entityIdentifier, entityDescription) in self.model.entityDescriptionsByEntityIdentifier {
|
||||
for (entityIdentifier, entityDescription) in self.schemaHistory.entityDescriptionsByEntityIdentifier {
|
||||
|
||||
switch entityIdentifier.category {
|
||||
|
||||
@@ -133,7 +126,7 @@ public final class DataStack: Equatable {
|
||||
public func entityTypesByName(for type: CoreStoreObject.Type) -> [EntityName: CoreStoreObject.Type] {
|
||||
|
||||
var entityTypesByName: [EntityName: CoreStoreObject.Type] = [:]
|
||||
for (entityIdentifier, entityDescription) in self.model.entityDescriptionsByEntityIdentifier {
|
||||
for (entityIdentifier, entityDescription) in self.schemaHistory.entityDescriptionsByEntityIdentifier {
|
||||
|
||||
switch entityIdentifier.category {
|
||||
|
||||
@@ -332,7 +325,7 @@ public final class DataStack: Equatable {
|
||||
)
|
||||
try storage.eraseStorageAndWait(
|
||||
metadata: metadata,
|
||||
soureModelHint: self.model[metadata]
|
||||
soureModelHint: self.schemaHistory.schema(for: metadata)?.rawModel()
|
||||
)
|
||||
let finalStoreOptions = storage.dictionary(forOptions: storage.localStorageOptions)
|
||||
_ = try self.createPersistentStoreFromStorage(
|
||||
@@ -425,7 +418,9 @@ public final class DataStack: Equatable {
|
||||
at: cacheFileURL,
|
||||
options: storeOptions
|
||||
)
|
||||
_ = try self.model[metadata].flatMap(storage.eraseStorageAndWait)
|
||||
_ = try self.schemaHistory
|
||||
.schema(for: metadata)
|
||||
.flatMap({ try storage.eraseStorageAndWait(soureModel: $0.rawModel()) })
|
||||
_ = try self.createPersistentStoreFromStorage(
|
||||
storage,
|
||||
finalURL: cacheFileURL,
|
||||
@@ -464,11 +459,10 @@ public final class DataStack: Equatable {
|
||||
internal let coordinator: NSPersistentStoreCoordinator
|
||||
internal let rootSavingContext: NSManagedObjectContext
|
||||
internal let mainContext: NSManagedObjectContext
|
||||
internal let model: NSManagedObjectModel
|
||||
internal let migrationChain: MigrationChain
|
||||
internal let schemaHistory: SchemaHistory
|
||||
internal let childTransactionQueue = DispatchQueue.serial("com.coreStore.dataStack.childTransactionQueue")
|
||||
internal let storeMetadataUpdateQueue = DispatchQueue.concurrent("com.coreStore.persistentStoreBarrierQueue")
|
||||
internal let migrationQueue: OperationQueue = {
|
||||
internal let migrationQueue: OperationQueue = cs_lazy {
|
||||
|
||||
let migrationQueue = OperationQueue()
|
||||
migrationQueue.maxConcurrentOperationCount = 1
|
||||
@@ -476,7 +470,7 @@ public final class DataStack: Equatable {
|
||||
migrationQueue.qualityOfService = .utility
|
||||
migrationQueue.underlyingQueue = DispatchQueue.serial("com.coreStore.migrationQueue", qos: .userInitiated)
|
||||
return migrationQueue
|
||||
}()
|
||||
}
|
||||
|
||||
internal func persistentStoreForStorage(_ storage: StorageInterface) -> NSPersistentStore? {
|
||||
|
||||
@@ -562,7 +556,7 @@ public final class DataStack: Equatable {
|
||||
|
||||
internal func entityDescription(for entityIdentifier: EntityIdentifier) -> NSEntityDescription? {
|
||||
|
||||
return self.model.entityDescriptionsByEntityIdentifier[entityIdentifier]
|
||||
return self.schemaHistory.entityDescriptionsByEntityIdentifier[entityIdentifier]
|
||||
}
|
||||
|
||||
|
||||
@@ -596,6 +590,30 @@ public final class DataStack: Equatable {
|
||||
|
||||
// MARK: Deprecated
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter model: the `NSManagedObjectModel` for the stack
|
||||
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
@available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModel instance as argument")
|
||||
public convenience init(model: NSManagedObjectModel, migrationChain: MigrationChain = nil) {
|
||||
|
||||
let modelVersion = migrationChain.leafVersions.first!
|
||||
self.init(
|
||||
schemaHistory: SchemaHistory(
|
||||
allSchema: [
|
||||
LegacyXcodeDataModel(
|
||||
modelName: modelVersion,
|
||||
model: model
|
||||
)
|
||||
],
|
||||
migrationChain: migrationChain,
|
||||
exactCurrentModelVersion: modelVersion
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `DataStack`'s model.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// DynamicModel.swift
|
||||
// CoreStoreSchema.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 John Rommel Estropia
|
||||
@@ -23,20 +23,31 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreGraphics
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DynamicModel
|
||||
// MARK: - CoreStoreSchema
|
||||
|
||||
public final class DynamicModel {
|
||||
public final class CoreStoreSchema: DynamicSchema {
|
||||
|
||||
public convenience init(version: String, entities: [EntityProtocol]) {
|
||||
public convenience init(modelVersion: String, _ entity: DynamicEntity, _ entities: DynamicEntity...) {
|
||||
|
||||
self.init(version: version, entitiesByConfiguration: [DataStack.defaultConfigurationName: entities])
|
||||
self.init(
|
||||
modelVersion: modelVersion,
|
||||
entities: [entity] + entities
|
||||
)
|
||||
}
|
||||
|
||||
public required init(version: String, entitiesByConfiguration: [String: [EntityProtocol]]) {
|
||||
public convenience init(modelVersion: String, entities: [DynamicEntity]) {
|
||||
|
||||
self.init(
|
||||
modelVersion: modelVersion,
|
||||
entitiesByConfiguration: [DataStack.defaultConfigurationName: entities]
|
||||
)
|
||||
}
|
||||
|
||||
public required init(modelVersion: String, entitiesByConfiguration: [String: [DynamicEntity]]) {
|
||||
|
||||
var actualEntitiesByConfiguration: [String: Set<AnyEntity>] = [:]
|
||||
for (configuration, entities) in entitiesByConfiguration {
|
||||
@@ -56,19 +67,57 @@ public final class DynamicModel {
|
||||
"Ambiguous entity types or entity names were found in the model. Ensure that the entity types and entity names are unique to each other. Entities: \(allEntities)"
|
||||
)
|
||||
|
||||
self.version = version
|
||||
self.modelVersion = modelVersion
|
||||
self.entitiesByConfiguration = actualEntitiesByConfiguration
|
||||
self.allEntities = allEntities
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DynamicSchema
|
||||
|
||||
public let modelVersion: ModelVersion
|
||||
|
||||
public func rawModel() -> NSManagedObjectModel {
|
||||
|
||||
if let cachedRawModel = self.cachedRawModel {
|
||||
|
||||
return cachedRawModel
|
||||
}
|
||||
let rawModel = NSManagedObjectModel()
|
||||
var entityDescriptionsByEntity: [AnyEntity: NSEntityDescription] = [:]
|
||||
for entity in self.allEntities {
|
||||
|
||||
let entityDescription = self.entityDescription(
|
||||
for: entity,
|
||||
initializer: CoreStoreSchema.firstPassCreateEntityDescription
|
||||
)
|
||||
entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription)
|
||||
}
|
||||
CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
|
||||
CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity)
|
||||
|
||||
rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
|
||||
for (configuration, entities) in self.entitiesByConfiguration {
|
||||
|
||||
rawModel.setEntities(
|
||||
entities
|
||||
.map({ entityDescriptionsByEntity[$0]! })
|
||||
.sorted(by: { $0.name! < $1.name! }),
|
||||
forConfigurationName: configuration
|
||||
)
|
||||
}
|
||||
self.cachedRawModel = rawModel
|
||||
return rawModel
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
// MARK: - AnyEntity
|
||||
|
||||
internal struct AnyEntity: EntityProtocol, Hashable {
|
||||
internal struct AnyEntity: DynamicEntity, Hashable {
|
||||
|
||||
internal init(_ entity: EntityProtocol) {
|
||||
internal init(_ entity: DynamicEntity) {
|
||||
|
||||
self.type = entity.type
|
||||
self.entityName = entity.entityName
|
||||
@@ -97,7 +146,7 @@ public final class DynamicModel {
|
||||
^ self.entityName.hashValue
|
||||
}
|
||||
|
||||
// MARK: EntityProtocol
|
||||
// MARK: DynamicEntity
|
||||
|
||||
internal let type: CoreStoreObject.Type
|
||||
internal let entityName: EntityName
|
||||
@@ -106,41 +155,30 @@ public final class DynamicModel {
|
||||
|
||||
// MARK: -
|
||||
|
||||
internal func createModel() -> NSManagedObjectModel {
|
||||
internal let entitiesByConfiguration: [String: Set<AnyEntity>]
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue")
|
||||
|
||||
private let allEntities: Set<AnyEntity>
|
||||
|
||||
private var entityDescriptionsByEntity: [CoreStoreSchema.AnyEntity: NSEntityDescription] = [:]
|
||||
private weak var cachedRawModel: NSManagedObjectModel?
|
||||
|
||||
private func entityDescription(for entity: CoreStoreSchema.AnyEntity, initializer: (CoreStoreSchema.AnyEntity) -> NSEntityDescription) -> NSEntityDescription {
|
||||
|
||||
let model = NSManagedObjectModel()
|
||||
let entityDescriptionsByEntity: [AnyEntity: NSEntityDescription] = ModelCache.performUpdate {
|
||||
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
|
||||
|
||||
var entityDescriptionsByEntity: [AnyEntity: NSEntityDescription] = [:]
|
||||
for entity in self.allEntities {
|
||||
|
||||
let entityDescription = ModelCache.entityDescription(
|
||||
for: entity,
|
||||
initializer: DynamicModel.firstPassCreateEntityDescription
|
||||
)
|
||||
entityDescriptionsByEntity[entity] = entityDescription
|
||||
}
|
||||
DynamicModel.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
|
||||
DynamicModel.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity)
|
||||
return entityDescriptionsByEntity
|
||||
return cachedEntityDescription
|
||||
}
|
||||
model.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
|
||||
for (configuration, entities) in self.entitiesByConfiguration {
|
||||
|
||||
model.setEntities(
|
||||
entities
|
||||
.map({ entityDescriptionsByEntity[$0]! })
|
||||
.sorted(by: { $0.name! < $1.name! }),
|
||||
forConfigurationName: configuration
|
||||
)
|
||||
}
|
||||
return model
|
||||
let entityDescription = withoutActuallyEscaping(initializer, do: { $0(entity) })
|
||||
self.entityDescriptionsByEntity[entity] = entityDescription
|
||||
return entityDescription
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate static func firstPassCreateEntityDescription(from entity: AnyEntity) -> NSEntityDescription {
|
||||
private static func firstPassCreateEntityDescription(from entity: AnyEntity) -> NSEntityDescription {
|
||||
|
||||
let entityDescription = NSEntityDescription()
|
||||
entityDescription.anyEntity = entity
|
||||
@@ -186,7 +224,7 @@ public final class DynamicModel {
|
||||
return entityDescription
|
||||
}
|
||||
|
||||
fileprivate static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [AnyEntity: NSEntityDescription]) {
|
||||
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [AnyEntity: NSEntityDescription]) {
|
||||
|
||||
var relationshipsByNameByEntity: [AnyEntity: [String: NSRelationshipDescription]] = [:]
|
||||
for (entity, entityDescription) in entityDescriptionsByEntity {
|
||||
@@ -207,7 +245,7 @@ public final class DynamicModel {
|
||||
if matchedEntities.isEmpty {
|
||||
|
||||
CoreStore.abort(
|
||||
"No \(cs_typeName("Entity<\(type)>")) instance found in the \(cs_typeName(DynamicModel.self))."
|
||||
"No \(cs_typeName("Entity<\(type)>")) instance found in the \(cs_typeName(CoreStoreSchema.self))."
|
||||
)
|
||||
}
|
||||
else {
|
||||
@@ -277,7 +315,7 @@ public final class DynamicModel {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate static func thirdPassConnectInheritanceTree(for entityDescriptionsByEntity: [AnyEntity: NSEntityDescription]) {
|
||||
private static func thirdPassConnectInheritanceTree(for entityDescriptionsByEntity: [AnyEntity: NSEntityDescription]) {
|
||||
|
||||
func connectBaseEntity(mirror: Mirror, entityDescription: NSEntityDescription) {
|
||||
|
||||
@@ -304,40 +342,4 @@ public final class DynamicModel {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let version: String
|
||||
private let allEntities: Set<AnyEntity>
|
||||
private let entitiesByConfiguration: [String: Set<AnyEntity>]
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ModelCache
|
||||
|
||||
fileprivate enum ModelCache {
|
||||
|
||||
fileprivate static func performUpdate<T>(_ closure: () -> T) -> T {
|
||||
|
||||
return self.barrierQueue.cs_barrierSync(closure)
|
||||
}
|
||||
|
||||
fileprivate static func entityDescription(for entity: DynamicModel.AnyEntity, initializer: (DynamicModel.AnyEntity) -> NSEntityDescription) -> NSEntityDescription {
|
||||
|
||||
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
|
||||
|
||||
return cachedEntityDescription
|
||||
}
|
||||
let entityDescription = withoutActuallyEscaping(initializer, do: { $0(entity) })
|
||||
self.entityDescriptionsByEntity[entity] = entityDescription
|
||||
return entityDescription
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.modelCacheBarrierQueue")
|
||||
|
||||
private static var entityDescriptionsByEntity: [DynamicModel.AnyEntity: NSEntityDescription] = [:]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// DynamicSchema.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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 CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DynamicSchema
|
||||
|
||||
public protocol DynamicSchema {
|
||||
|
||||
var modelVersion: ModelVersion { get }
|
||||
|
||||
func rawModel() -> NSManagedObjectModel
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// LegacyXcodeDataModel.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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 CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - LegacyXcodeDataModel
|
||||
|
||||
public final class LegacyXcodeDataModel: DynamicSchema {
|
||||
|
||||
public required init(modelName: ModelVersion, model: NSManagedObjectModel) {
|
||||
|
||||
self.modelVersion = modelName
|
||||
self.model = model
|
||||
}
|
||||
|
||||
|
||||
// MARK: DynamicSchema
|
||||
|
||||
public let modelVersion: ModelVersion
|
||||
|
||||
public func rawModel() -> NSManagedObjectModel {
|
||||
|
||||
return self.model
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let model: NSManagedObjectModel
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// XcodeDataModel.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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 CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - XcodeDataModel
|
||||
|
||||
public final class XcodeDataModel: DynamicSchema {
|
||||
|
||||
public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSManagedObjectModel(contentsOf: modelVersionFileURL) != nil,
|
||||
"Could not find the \"\(modelVersion).mom\" version file for the model at URL \"\(modelVersionFileURL)\"."
|
||||
)
|
||||
|
||||
self.modelVersion = modelVersion
|
||||
self.modelVersionFileURL = modelVersionFileURL
|
||||
}
|
||||
|
||||
|
||||
// MARK: DynamicSchema
|
||||
|
||||
public let modelVersion: ModelVersion
|
||||
|
||||
public func rawModel() -> NSManagedObjectModel {
|
||||
|
||||
if let cachedRawModel = self.cachedRawModel {
|
||||
|
||||
return cachedRawModel
|
||||
}
|
||||
if let rawModel = NSManagedObjectModel(contentsOf: self.modelVersionFileURL) {
|
||||
|
||||
self.cachedRawModel = rawModel
|
||||
return rawModel
|
||||
}
|
||||
CoreStore.abort("Could not create an \(cs_typeName(NSManagedObjectModel.self)) from the model at URL \"\(self.modelVersionFileURL)\".")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let modelVersionFileURL: URL
|
||||
|
||||
private lazy var rootModelFileURL: URL = cs_lazy { [unowned self] in
|
||||
|
||||
return self.modelVersionFileURL.deletingLastPathComponent()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private weak var cachedRawModel: NSManagedObjectModel?
|
||||
}
|
||||
@@ -28,9 +28,9 @@ import Foundation
|
||||
import ObjectiveC
|
||||
|
||||
|
||||
// MARK: - EntityProtocol
|
||||
// MARK: - DynamicEntity
|
||||
|
||||
public protocol EntityProtocol {
|
||||
public protocol DynamicEntity {
|
||||
|
||||
var type: CoreStoreObject.Type { get }
|
||||
var entityName: EntityName { get }
|
||||
@@ -39,7 +39,7 @@ public protocol EntityProtocol {
|
||||
|
||||
// MARK: Entity
|
||||
|
||||
public struct Entity<O: CoreStoreObject>: EntityProtocol {
|
||||
public struct Entity<O: CoreStoreObject>: DynamicEntity, Hashable {
|
||||
|
||||
public init(_ entityName: String) {
|
||||
|
||||
@@ -47,136 +47,54 @@ public struct Entity<O: CoreStoreObject>: EntityProtocol {
|
||||
self.entityName = entityName
|
||||
}
|
||||
|
||||
// MARK: EntityProtocol
|
||||
public init(_ type: O.Type, _ entityName: String) {
|
||||
|
||||
self.type = type
|
||||
self.entityName = entityName
|
||||
}
|
||||
|
||||
|
||||
// MARK: - VersionHash
|
||||
|
||||
public struct VersionHash: ExpressibleByArrayLiteral {
|
||||
|
||||
let hash: Data
|
||||
|
||||
public init(_ hash: Data) {
|
||||
|
||||
self.hash = hash
|
||||
}
|
||||
|
||||
|
||||
// MARK: ExpressibleByArrayLiteral
|
||||
|
||||
public typealias Element = UInt8
|
||||
|
||||
public init(arrayLiteral elements: UInt8...) {
|
||||
|
||||
self.hash = Data(bytes: elements)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: DynamicEntity
|
||||
|
||||
public let type: CoreStoreObject.Type
|
||||
public let entityName: EntityName
|
||||
}
|
||||
|
||||
|
||||
// MARK: - EntityIdentifier
|
||||
|
||||
internal struct EntityIdentifier: Hashable {
|
||||
|
||||
// MARK: - Category
|
||||
|
||||
internal enum Category: Int {
|
||||
|
||||
case coreData
|
||||
case coreStore
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
|
||||
internal let category: Category
|
||||
internal let interfacedClassName: String
|
||||
|
||||
internal init(_ type: NSManagedObject.Type) {
|
||||
|
||||
self.category = .coreData
|
||||
self.interfacedClassName = String(reflecting: type)
|
||||
}
|
||||
|
||||
internal init(_ type: CoreStoreObject.Type) {
|
||||
|
||||
self.category = .coreStore
|
||||
self.interfacedClassName = String(reflecting: type)
|
||||
}
|
||||
|
||||
internal init(_ type: DynamicObject.Type) {
|
||||
|
||||
switch type {
|
||||
|
||||
case let type as NSManagedObject.Type:
|
||||
self.init(type)
|
||||
|
||||
case let type as CoreStoreObject.Type:
|
||||
self.init(type)
|
||||
|
||||
default:
|
||||
CoreStore.abort("\(cs_typeName(DynamicObject.self)) is not meant to be implemented by external types.")
|
||||
}
|
||||
}
|
||||
|
||||
internal init(_ entityDescription: NSEntityDescription) {
|
||||
|
||||
if let entity = entityDescription.anyEntity {
|
||||
|
||||
self.category = .coreStore
|
||||
self.interfacedClassName = NSStringFromClass(entity.type)
|
||||
}
|
||||
else {
|
||||
|
||||
self.category = .coreData
|
||||
self.interfacedClassName = entityDescription.managedObjectClassName!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
static func == (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool {
|
||||
public static func == (lhs: Entity, rhs: Entity) -> Bool {
|
||||
|
||||
return lhs.category == rhs.category
|
||||
&& lhs.interfacedClassName == rhs.interfacedClassName
|
||||
return lhs.type == rhs.type
|
||||
&& lhs.entityName == rhs.entityName
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
var hashValue: Int {
|
||||
|
||||
return self.category.hashValue
|
||||
^ self.interfacedClassName.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSEntityDescription
|
||||
|
||||
internal extension NSEntityDescription {
|
||||
|
||||
@nonobjc
|
||||
internal var anyEntity: DynamicModel.AnyEntity? {
|
||||
|
||||
get {
|
||||
|
||||
guard let userInfo = self.userInfo,
|
||||
let typeName = userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] as! String?,
|
||||
let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return DynamicModel.AnyEntity(
|
||||
type: NSClassFromString(typeName) as! CoreStoreObject.Type,
|
||||
entityName: entityName
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
if let newValue = newValue {
|
||||
|
||||
self.userInfo = [
|
||||
UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type),
|
||||
UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName
|
||||
]
|
||||
}
|
||||
else {
|
||||
|
||||
self.userInfo = [:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: - UserInfoKey
|
||||
|
||||
fileprivate enum UserInfoKey {
|
||||
|
||||
fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName"
|
||||
fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName"
|
||||
public var hashValue: Int {
|
||||
|
||||
return ObjectIdentifier(self.type).hashValue
|
||||
}
|
||||
}
|
||||
|
||||
227
Sources/Setup/Dynamic Models/SchemaHistory.swift
Normal file
227
Sources/Setup/Dynamic Models/SchemaHistory.swift
Normal file
@@ -0,0 +1,227 @@
|
||||
//
|
||||
// SchemaHistory.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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 CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - SchemaHistory
|
||||
|
||||
public final class SchemaHistory: ExpressibleByArrayLiteral {
|
||||
|
||||
public let currentModelVersion: ModelVersion
|
||||
public let migrationChain: MigrationChain
|
||||
|
||||
public convenience init(modelName: XcodeDataModelFileName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
|
||||
|
||||
guard let modelFilePath = bundle.path(forResource: modelName, ofType: "momd") else {
|
||||
|
||||
// For users migrating from very old Xcode versions: Old xcdatamodel files are not contained inside xcdatamodeld (with a "d"), and will thus fail this check. If that was the case, create a new xcdatamodeld file and copy all contents into the new model.
|
||||
let foundModels = bundle
|
||||
.paths(forResourcesOfType: "momd", inDirectory: nil)
|
||||
.map({ ($0 as NSString).lastPathComponent })
|
||||
CoreStore.abort("Could not find \"\(modelName).momd\" from the bundle \"\(bundle.bundleIdentifier ?? "<nil>")\". Other model files in bundle: \(foundModels.coreStoreDumpString)")
|
||||
}
|
||||
|
||||
let modelFileURL = URL(fileURLWithPath: modelFilePath)
|
||||
let versionInfoPlistURL = modelFileURL.appendingPathComponent("VersionInfo.plist", isDirectory: false)
|
||||
|
||||
guard let versionInfo = NSDictionary(contentsOf: versionInfoPlistURL),
|
||||
let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else {
|
||||
|
||||
CoreStore.abort("Could not load \(cs_typeName(NSManagedObjectModel.self)) metadata from path \"\(versionInfoPlistURL)\".")
|
||||
}
|
||||
|
||||
let modelVersions = Set(versionHashes.keys)
|
||||
let modelVersionHints = migrationChain.leafVersions
|
||||
let currentModelVersion: String
|
||||
if let plistModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String,
|
||||
modelVersionHints.isEmpty || modelVersionHints.contains(plistModelVersion) {
|
||||
|
||||
currentModelVersion = plistModelVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.intersection(modelVersionHints).first {
|
||||
|
||||
CoreStore.log(
|
||||
.warning,
|
||||
message: "The \(cs_typeName(MigrationChain.self)) leaf versions do not include the model file's current version. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
|
||||
|
||||
if !modelVersionHints.isEmpty {
|
||||
|
||||
CoreStore.log(
|
||||
.warning,
|
||||
message: "The \(cs_typeName(MigrationChain.self)) leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
}
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.abort("No model files were found in URL \"\(modelFileURL)\".")
|
||||
}
|
||||
var allSchema: [DynamicSchema] = []
|
||||
for modelVersion in modelVersions {
|
||||
|
||||
let fileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
allSchema.append(XcodeDataModel(modelVersion: modelVersion, modelVersionFileURL: fileURL))
|
||||
}
|
||||
self.init(
|
||||
allSchema: allSchema,
|
||||
migrationChain: migrationChain,
|
||||
exactCurrentModelVersion: currentModelVersion
|
||||
)
|
||||
}
|
||||
|
||||
public convenience init(_ schema: DynamicSchema, _ otherSchema: DynamicSchema..., migrationChain: MigrationChain = nil, exactCurrentModelVersion: String? = nil) {
|
||||
|
||||
self.init(
|
||||
allSchema: [schema] + otherSchema,
|
||||
migrationChain: migrationChain,
|
||||
exactCurrentModelVersion: exactCurrentModelVersion
|
||||
)
|
||||
}
|
||||
|
||||
public required init(allSchema: [DynamicSchema], migrationChain: MigrationChain = nil, exactCurrentModelVersion: String? = nil) {
|
||||
|
||||
if allSchema.isEmpty {
|
||||
|
||||
CoreStore.abort("The \"allSchema\" argument of the \(cs_typeName(SchemaHistory.self)) initializer cannot be empty.")
|
||||
}
|
||||
|
||||
var schemaByVersion: [ModelVersion: DynamicSchema] = [:]
|
||||
for schema in allSchema {
|
||||
|
||||
let modelVersion = schema.modelVersion
|
||||
CoreStore.assert(
|
||||
schemaByVersion[modelVersion] == nil,
|
||||
"Multiple model schema found for model version \"\(modelVersion)\"."
|
||||
)
|
||||
schemaByVersion[modelVersion] = schema
|
||||
}
|
||||
let modelVersions = Set(schemaByVersion.keys)
|
||||
let currentModelVersion: ModelVersion
|
||||
if let exactCurrentModelVersion = exactCurrentModelVersion {
|
||||
|
||||
if !migrationChain.isEmpty && !migrationChain.contains(exactCurrentModelVersion) {
|
||||
|
||||
CoreStore.abort("An \"exactCurrentModelVersion\" argument was provided to \(cs_typeName(SchemaHistory.self)) initializer but a matching schema could not be found from the provided \(cs_typeName(MigrationChain.self)).")
|
||||
}
|
||||
if schemaByVersion[exactCurrentModelVersion] == nil {
|
||||
|
||||
CoreStore.abort("An \"exactCurrentModelVersion\" argument was provided to \(cs_typeName(SchemaHistory.self)) initializer but a matching schema could not be found from the \(cs_typeName(DynamicSchema.self)) list.")
|
||||
}
|
||||
currentModelVersion = exactCurrentModelVersion
|
||||
}
|
||||
else if migrationChain.isEmpty && schemaByVersion.count == 1 {
|
||||
|
||||
currentModelVersion = schemaByVersion.keys.first!
|
||||
}
|
||||
else {
|
||||
|
||||
let candidateVersions = modelVersions.intersection(migrationChain.leafVersions)
|
||||
switch candidateVersions.count {
|
||||
|
||||
case 0:
|
||||
CoreStore.abort("None of the \(cs_typeName(MigrationChain.self)) leaf versions provided to the \(cs_typeName(SchemaHistory.self)) initializer matches any scheme from the \(cs_typeName(DynamicSchema.self)) list.")
|
||||
|
||||
case 1:
|
||||
currentModelVersion = candidateVersions.first!
|
||||
|
||||
default:
|
||||
CoreStore.abort("Could not resolve the \(cs_typeName(SchemaHistory.self)) current model version because the \(cs_typeName(MigrationChain.self)) have ambiguous leaf versions: \(candidateVersions)")
|
||||
}
|
||||
}
|
||||
self.schemaByVersion = schemaByVersion
|
||||
self.migrationChain = migrationChain
|
||||
self.currentModelVersion = currentModelVersion
|
||||
self.rawModel = schemaByVersion[currentModelVersion]!.rawModel()
|
||||
}
|
||||
|
||||
|
||||
// MARK: ExpressibleByArrayLiteral
|
||||
|
||||
public typealias Element = DynamicSchema
|
||||
|
||||
public convenience init(arrayLiteral elements: DynamicSchema...) {
|
||||
|
||||
self.init(
|
||||
allSchema: elements,
|
||||
migrationChain: MigrationChain(elements.map({ $0.modelVersion })),
|
||||
exactCurrentModelVersion: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let schemaByVersion: [ModelVersion: DynamicSchema]
|
||||
internal let rawModel: NSManagedObjectModel
|
||||
|
||||
internal private(set) lazy var entityDescriptionsByEntityIdentifier: [EntityIdentifier: NSEntityDescription] = cs_lazy { [unowned self] in
|
||||
|
||||
var mapping: [EntityIdentifier: NSEntityDescription] = [:]
|
||||
self.rawModel.entities.forEach { (entityDescription) in
|
||||
|
||||
let entityIdentifier = EntityIdentifier(entityDescription)
|
||||
mapping[entityIdentifier] = entityDescription
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
internal func rawModel(for modelVersion: ModelVersion) -> NSManagedObjectModel? {
|
||||
|
||||
if modelVersion == self.currentModelVersion {
|
||||
|
||||
return self.rawModel
|
||||
}
|
||||
return self.schemaByVersion[modelVersion]?.rawModel()
|
||||
}
|
||||
|
||||
internal func schema(for storeMetadata: [String: Any]) -> DynamicSchema? {
|
||||
|
||||
guard let modelHashes = storeMetadata[NSStoreModelVersionHashesKey] as! [String: Data]? else {
|
||||
|
||||
return nil
|
||||
}
|
||||
for (_, schema) in self.schemaByVersion {
|
||||
|
||||
let rawModel = schema.rawModel()
|
||||
if modelHashes == rawModel.entityVersionHashesByName {
|
||||
|
||||
return schema
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func mergedModels() -> [NSManagedObjectModel] {
|
||||
|
||||
return self.schemaByVersion.values.map({ $0.rawModel() })
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static let defaultRootDirectory: URL = {
|
||||
internal static let defaultRootDirectory: URL = cs_lazy {
|
||||
|
||||
#if os(tvOS)
|
||||
let systemDirectorySearchPath = FileManager.SearchPathDirectory.cachesDirectory
|
||||
@@ -244,7 +244,7 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
|
||||
return FileManager.default.urls(
|
||||
for: systemDirectorySearchPath,
|
||||
in: .userDomainMask).first!
|
||||
}()
|
||||
}
|
||||
|
||||
internal static let defaultFileURL = LegacySQLiteStore.defaultRootDirectory
|
||||
.appendingPathComponent(DataStack.applicationName, isDirectory: false)
|
||||
|
||||
@@ -230,7 +230,7 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static let defaultRootDirectory: URL = {
|
||||
internal static let defaultRootDirectory: URL = cs_lazy {
|
||||
|
||||
#if os(tvOS)
|
||||
let systemDirectorySearchPath = FileManager.SearchPathDirectory.cachesDirectory
|
||||
@@ -246,7 +246,7 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
|
||||
Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack",
|
||||
isDirectory: true
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
internal static let defaultFileURL = SQLiteStore.defaultRootDirectory
|
||||
.appendingPathComponent(
|
||||
|
||||
Reference in New Issue
Block a user