WIP: allow migrations for CoreStoreObjects

This commit is contained in:
John Estropia
2017-04-11 19:13:35 +09:00
parent e5ef4992d3
commit 9f3db61ff7
27 changed files with 1146 additions and 691 deletions

View File

@@ -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] = [:]
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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?
}

View File

@@ -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
}
}

View 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() })
}
}