diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index fd28c3b..406c4ef 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -63,7 +63,6 @@ 82BA18D41C4BBD7100A0916E /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */; }; 82BA18D51C4BBD7100A0916E /* NSManagedObjectContext+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */; }; 82BA18D61C4BBD7100A0916E /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; }; - 82BA18D71C4BBD7100A0916E /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; 82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* WeakObject.swift */; }; 82BA18DC1C4BBD9C00A0916E /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; 82BA18DD1C4BBE1400A0916E /* NSFetchedResultsController+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */; }; @@ -88,7 +87,14 @@ B51260801E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */; }; B51260811E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */; }; B51260821E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */; }; - B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; + B51260891E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */; }; + B512608A1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */; }; + B512608B1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */; }; + B512608C1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */; }; + B51260931E9B28F100402229 /* EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* EntityIdentifier.swift */; }; + B51260941E9B28F100402229 /* EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* EntityIdentifier.swift */; }; + B51260951E9B28F100402229 /* EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* EntityIdentifier.swift */; }; + B51260961E9B28F100402229 /* EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* EntityIdentifier.swift */; }; B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; }; B51FE5AD1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; }; B51FE5AE1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; }; @@ -199,8 +205,27 @@ B52DD1C71BE1F94600949AFE /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */; }; B52DD1C81BE1F94600949AFE /* NSManagedObjectContext+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */; }; B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; }; - B52DD1CA1BE1F94600949AFE /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; B52DD1CB1BE1F94600949AFE /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* WeakObject.swift */; }; + B52F742F1E9B50D0005F3DAC /* SchemaHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */; }; + B52F74301E9B50D0005F3DAC /* SchemaHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */; }; + B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */; }; + B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */; }; + B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; + B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; + B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; + B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */; }; + B52F74411E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; + B52F74421E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; + B52F74431E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; + B52F74441E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */; }; + B52F74451E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; + B52F74461E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; + B52F74471E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; + B52F74481E9B8724005F3DAC /* XcodeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */; }; + B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; + B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; + B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; + B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */; }; B52FD3AA1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */; }; B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */; }; B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */; }; @@ -358,7 +383,6 @@ B56321B21BD6521C006C9394 /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */; }; B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */; }; B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; }; - B56321B51BD6521C006C9394 /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; B56321B61BD6521C006C9394 /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* WeakObject.swift */; }; B56507941D3930BC000596DA /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507931D3930BC000596DA /* CoreData.framework */; }; B56507961D3930C1000596DA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507951D3930C1000596DA /* Foundation.framework */; }; @@ -417,10 +441,6 @@ B5C976E81C6E3A5D00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */; }; B5C976E91C6E3A5E00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */; }; B5D1E22C19FA9FBC003B2874 /* CoreStoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* CoreStoreError.swift */; }; - B5D339AF1E925BF200C880DE /* DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339AE1E925BF200C880DE /* DynamicModel.swift */; }; - B5D339B01E925BF200C880DE /* DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339AE1E925BF200C880DE /* DynamicModel.swift */; }; - B5D339B11E925BF200C880DE /* DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339AE1E925BF200C880DE /* DynamicModel.swift */; }; - B5D339B21E925BF200C880DE /* DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339AE1E925BF200C880DE /* DynamicModel.swift */; }; B5D339B41E925C2B00C880DE /* DynamicModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339B31E925C2B00C880DE /* DynamicModelTests.swift */; }; B5D339B51E925C2B00C880DE /* DynamicModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339B31E925C2B00C880DE /* DynamicModelTests.swift */; }; B5D339B61E925C2B00C880DE /* DynamicModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339B31E925C2B00C880DE /* DynamicModelTests.swift */; }; @@ -653,7 +673,8 @@ B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableAttributeType.swift; sourceTree = ""; }; B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Convenience.swift"; sourceTree = ""; }; - B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = ""; }; + B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+DynamicModel.swift"; sourceTree = ""; }; + B51260921E9B28F100402229 /* EntityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityIdentifier.swift; sourceTree = ""; }; B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+CustomDebugStringConvertible.swift"; sourceTree = ""; }; B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFetchedResultsController+Convenience.swift"; sourceTree = ""; }; B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverTests.swift; sourceTree = ""; }; @@ -671,6 +692,11 @@ B529C2031CA4A2DB007E7EBD /* CSSaveResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSaveResult.swift; sourceTree = ""; }; B52DD1741BE1F8CC00949AFE /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B52DD17D1BE1F8CC00949AFE /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaHistory.swift; sourceTree = ""; }; + B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicSchema.swift; sourceTree = ""; }; + B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyXcodeDataModel.swift; sourceTree = ""; }; + B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeDataModel.swift; sourceTree = ""; }; + B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreSchema.swift; sourceTree = ""; }; B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Logging.swift"; sourceTree = ""; }; B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+CoreStore.swift"; sourceTree = ""; }; B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = ""; }; @@ -733,7 +759,6 @@ B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeDataTransaction+Observing.swift"; sourceTree = ""; }; B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreFetchedResultsController.swift; sourceTree = ""; }; B5D1E22B19FA9FBC003B2874 /* CoreStoreError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreError.swift; sourceTree = ""; }; - B5D339AE1E925BF200C880DE /* DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicModel.swift; sourceTree = ""; }; B5D339B31E925C2B00C880DE /* DynamicModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicModelTests.swift; sourceTree = ""; }; B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreObject.swift; sourceTree = ""; }; B5D339DC1E9489C700C880DE /* DynamicObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicObject.swift; sourceTree = ""; }; @@ -1012,6 +1037,17 @@ name = Observing; sourceTree = ""; }; + B52F74391E9B8724005F3DAC /* Dynamic Schema */ = { + isa = PBXGroup; + children = ( + B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */, + B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */, + B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */, + B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */, + ); + path = "Dynamic Schema"; + sourceTree = ""; + }; B53FBA101CAB607000F0D40A /* Convenience */ = { isa = PBXGroup; children = ( @@ -1092,8 +1128,9 @@ isa = PBXGroup; children = ( B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */, - B5D339AE1E925BF200C880DE /* DynamicModel.swift */, + B52F74391E9B8724005F3DAC /* Dynamic Schema */, B5D339DC1E9489C700C880DE /* DynamicObject.swift */, + B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */, B5D339E61E9493A500C880DE /* Entity.swift */, B5D33A001E96012400C880DE /* Relationship.swift */, B5D339E11E948C3600C880DE /* Value.swift */, @@ -1289,6 +1326,8 @@ children = ( B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */, B526613F1CADD585007B85D9 /* CoreStoreFetchRequest+CoreStore.swift */, + B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */, + B51260921E9B28F100402229 /* EntityIdentifier.swift */, B54A6A541BA15F2A007870FD /* FetchedResultsControllerDelegate.swift */, B5E834BA1B7691F3001D3D50 /* Functions.swift */, B5FAD6AB1B51285300714891 /* MigrationManager.swift */, @@ -1299,7 +1338,6 @@ B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */, B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */, B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */, - B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */, B5FEC18D1C9166E200532541 /* NSPersistentStore+Setup.swift */, B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */, B5E84F2D1AFF849C0064E85B /* WeakObject.swift */, @@ -1633,6 +1671,7 @@ B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, B5C976E71C6E3A5A00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */, B5F1DA901B9AA991007C5CBB /* ImportableUniqueObject.swift in Sources */, + B51260891E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */, B529C2041CA4A2DB007E7EBD /* CSSaveResult.swift in Sources */, B5D1E22C19FA9FBC003B2874 /* CoreStoreError.swift in Sources */, @@ -1645,6 +1684,7 @@ B5E1B5981CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A5F1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AA1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, + B52F74411E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, B54A6A551BA15F2A007870FD /* FetchedResultsControllerDelegate.swift in Sources */, B5D339E21E948C3600C880DE /* Value.swift in Sources */, @@ -1676,6 +1716,7 @@ B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */, B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */, + B51260931E9B28F100402229 /* EntityIdentifier.swift in Sources */, B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */, B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D21C991B3E00B5CEFA /* CSDataStack.swift in Sources */, @@ -1696,6 +1737,7 @@ B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */, B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */, B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, + B52F74451E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */, @@ -1711,6 +1753,7 @@ B5A5F2661CAEC50F004AB9AF /* CSSelect.swift in Sources */, B5ECDBE51CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */, B5519A4A1CA1F4FB002BEF78 /* CSError.swift in Sources */, + B52F742F1E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */, B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */, B5D339EC1E9495E500C880DE /* Attribute+Querying.swift in Sources */, @@ -1719,8 +1762,7 @@ B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */, B5E222231CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */, - B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */, - B5D339AF1E925BF200C880DE /* DynamicModel.swift in Sources */, + B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B5E2222A1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */, @@ -1744,6 +1786,7 @@ B5E84F211AFF84860064E85B /* CoreStore+Observing.swift in Sources */, B559CD431CAA8B6300E4D58B /* CSSetupResult.swift in Sources */, B5FE4DA71C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, + B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, B5ECDBEC1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, @@ -1799,6 +1842,7 @@ B5ECDBFB1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, B5C976E81C6E3A5D00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */, 82BA18A21C4BBD1D00A0916E /* CoreStoreError.swift in Sources */, + B512608A1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, 82BA18B21C4BBD3900A0916E /* ImportableObject.swift in Sources */, B529C2061CA4A2DB007E7EBD /* CSSaveResult.swift in Sources */, 82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */, @@ -1811,6 +1855,7 @@ B5E1B59A1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A601CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, + B52F74421E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B51FE5AD1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B5D339E31E948C3600C880DE /* Value.swift in Sources */, @@ -1842,6 +1887,7 @@ B546F9741C9C553300D5AC55 /* SetupResult.swift in Sources */, 82BA18B11C4BBD3100A0916E /* SaveResult.swift in Sources */, 82BA18DD1C4BBE1400A0916E /* NSFetchedResultsController+Convenience.swift in Sources */, + B51260941E9B28F100402229 /* EntityIdentifier.swift in Sources */, B5FE4DA81C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */, @@ -1862,6 +1908,7 @@ B5ECDC131CA816E500C7F112 /* CSTweak.swift in Sources */, 82BA18C91C4BBD5900A0916E /* MigrationType.swift in Sources */, 82BA18D01C4BBD7100A0916E /* MigrationManager.swift in Sources */, + B52F74461E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, 82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */, B59851491C90289D00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E1B5A41CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, @@ -1876,7 +1923,7 @@ B5D339DE1E9489C700C880DE /* DynamicObject.swift in Sources */, B5ECDBE71CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */, B5519A4B1CA1F4FB002BEF78 /* CSError.swift in Sources */, - 82BA18D71C4BBD7100A0916E /* NSManagedObjectModel+Setup.swift in Sources */, + B52F74301E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, 82BA18C31C4BBD5300A0916E /* ObjectObserver.swift in Sources */, 82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */, B5D339ED1E9495E500C880DE /* Attribute+Querying.swift in Sources */, @@ -1885,8 +1932,8 @@ B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, 82BA18C41C4BBD5300A0916E /* ListMonitor.swift in Sources */, 82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */, + B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, - B5D339B01E925BF200C880DE /* DynamicModel.swift in Sources */, B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, 82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */, 82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */, @@ -1910,6 +1957,7 @@ B559CD451CAA8B6300E4D58B /* CSSetupResult.swift in Sources */, 82BA18B81C4BBD4200A0916E /* ClauseTypes.swift in Sources */, B5ECDBEE1CA6BF2000C7F112 /* CSFrom.swift in Sources */, + B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, 82BA18D61C4BBD7100A0916E /* NSManagedObjectContext+Transaction.swift in Sources */, 82BA18B91C4BBD4A00A0916E /* From.swift in Sources */, B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */, @@ -1965,6 +2013,7 @@ B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5677D411CD3B1E400322BFC /* ICloudStoreObserver.swift in Sources */, B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */, + B512608C1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, B5ECDC151CA816E500C7F112 /* CSTweak.swift in Sources */, B546F9761C9C553300D5AC55 /* SetupResult.swift in Sources */, B53FBA161CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, @@ -1977,6 +2026,7 @@ B5ECDC211CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B52DD1C21BE1F94600949AFE /* MigrationManager.swift in Sources */, B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, + B52F74441E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */, B5D339E51E948C3600C880DE /* Value.swift in Sources */, @@ -2008,6 +2058,7 @@ B52DD1BD1BE1F94300949AFE /* NSManagedObject+Convenience.swift in Sources */, B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */, B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, + B51260961E9B28F100402229 /* EntityIdentifier.swift in Sources */, B5ECDBE31CA6BB2B00C7F112 /* CSBaseDataTransaction+Querying.swift in Sources */, B5ECDC031CA80CBA00C7F112 /* CSWhere.swift in Sources */, B52DD1AC1BE1F93900949AFE /* Select.swift in Sources */, @@ -2028,6 +2079,7 @@ B5ECDC091CA8138100C7F112 /* CSOrderBy.swift in Sources */, B52DD1A51BE1F92F00949AFE /* ImportableUniqueObject.swift in Sources */, B5E222271CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, + B52F74481E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52DD19C1BE1F92C00949AFE /* Into.swift in Sources */, B5FE4DA51C8481E100FA6A91 /* StorageInterface.swift in Sources */, @@ -2036,13 +2088,13 @@ B5FE4DAA1C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B52DD1AF1BE1F93900949AFE /* GroupBy.swift in Sources */, B52DD1B01BE1F93900949AFE /* Tweak.swift in Sources */, - B52DD1CA1BE1F94600949AFE /* NSManagedObjectModel+Setup.swift in Sources */, B52DD1A41BE1F92F00949AFE /* ImportableObject.swift in Sources */, B5220E161D13067C009BC71E /* ObjectMonitor.swift in Sources */, B5D339E01E9489C700C880DE /* DynamicObject.swift in Sources */, B52DD1AE1BE1F93900949AFE /* OrderBy.swift in Sources */, B52DD1BA1BE1F94000949AFE /* MigrationChain.swift in Sources */, B50392FB1C479640009900CA /* NSManagedObject+Transaction.swift in Sources */, + B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B52DD1A31BE1F92C00949AFE /* SaveResult.swift in Sources */, B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */, B5D339EF1E9495E500C880DE /* Attribute+Querying.swift in Sources */, @@ -2051,8 +2103,8 @@ B52DD1C11BE1F94600949AFE /* Functions.swift in Sources */, B5220E1A1D130791009BC71E /* CoreStoreFetchedResultsController.swift in Sources */, B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, + B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B59FA0B21CCBACA8007C9BCA /* ICloudStore.swift in Sources */, - B5D339B21E925BF200C880DE /* DynamicModel.swift in Sources */, B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */, B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */, B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, @@ -2076,6 +2128,7 @@ B52DD19B1BE1F92800949AFE /* CoreStoreLogger.swift in Sources */, B52DD1991BE1F92800949AFE /* DefaultLogger.swift in Sources */, B5220E201D130813009BC71E /* CSObjectMonitor.swift in Sources */, + B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */, B5220E171D1306DF009BC71E /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA081CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */, @@ -2131,6 +2184,7 @@ B5ECDBFC1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, B5C976E91C6E3A5E00B1AF90 /* CoreStoreFetchedResultsController.swift in Sources */, B56321801BD65216006C9394 /* CoreStoreError.swift in Sources */, + B512608B1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, B56321AD1BD6521C006C9394 /* MigrationManager.swift in Sources */, B529C2071CA4A2DC007E7EBD /* CSSaveResult.swift in Sources */, B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */, @@ -2143,6 +2197,7 @@ B5519A611CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B5FE4DAE1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, + B52F74431E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, B51FE5AE1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */, B5D339E41E948C3600C880DE /* Value.swift in Sources */, @@ -2174,6 +2229,7 @@ B56321981BD65216006C9394 /* Where.swift in Sources */, B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B5FE4DA91C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, + B51260951E9B28F100402229 /* EntityIdentifier.swift in Sources */, B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D41C991B3E00B5CEFA /* CSDataStack.swift in Sources */, B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */, @@ -2194,6 +2250,7 @@ B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */, B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */, B598514A1C90289E00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, + B52F74471E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, B5FEC1901C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */, B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */, B5E1B5A51CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, @@ -2208,7 +2265,7 @@ B5D339DF1E9489C700C880DE /* DynamicObject.swift in Sources */, B5519A4C1CA1F4FB002BEF78 /* CSError.swift in Sources */, B563219B1BD65216006C9394 /* Tweak.swift in Sources */, - B56321B51BD6521C006C9394 /* NSManagedObjectModel+Setup.swift in Sources */, + B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */, B56321991BD65216006C9394 /* OrderBy.swift in Sources */, B5D339EE1E9495E500C880DE /* Attribute+Querying.swift in Sources */, @@ -2217,8 +2274,8 @@ B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, B56321A21BD65216006C9394 /* ListObserver.swift in Sources */, B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */, + B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, - B5D339B11E925BF200C880DE /* DynamicModel.swift in Sources */, B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */, B56321B61BD6521C006C9394 /* WeakObject.swift in Sources */, @@ -2242,6 +2299,7 @@ B559CD461CAA8B6300E4D58B /* CSSetupResult.swift in Sources */, B56321A61BD65216006C9394 /* MigrationType.swift in Sources */, B5ECDBEF1CA6BF2000C7F112 /* CSFrom.swift in Sources */, + B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */, B56321861BD65216006C9394 /* CoreStoreLogger.swift in Sources */, B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */, diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift index 8c952ad..b73bcac 100644 --- a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift @@ -60,7 +60,7 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD } for model in models { - if model.version == storeVersion { + if model.schemaHistory.currentModelVersion == storeVersion { return model } @@ -141,29 +141,35 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD // MARK: Private - private typealias ModelMetadata = (label: String, version: String, entityType: NSManagedObject.Type, migrationChain: MigrationChain) + private typealias ModelMetadata = (label: String, entityType: NSManagedObject.Type, schemaHistory: SchemaHistory) private let models: [ModelMetadata] = [ ( label: "Model V1", - version: "MigrationDemo", entityType: OrganismV1.self, - migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"] + schemaHistory: SchemaHistory( + modelName: "MigrationDemo", + migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"] + ) ), ( label: "Model V2", - version: "MigrationDemoV2", entityType: OrganismV2.self, - migrationChain: [ - "MigrationDemo": "MigrationDemoV2", - "MigrationDemoV3": "MigrationDemoV2" - ] + schemaHistory: SchemaHistory( + modelName: "MigrationDemo", + migrationChain: [ + "MigrationDemo": "MigrationDemoV2", + "MigrationDemoV3": "MigrationDemoV2" + ] + ) ), ( label: "Model V3", - version: "MigrationDemoV3", entityType: OrganismV3.self, - migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"] + schemaHistory: SchemaHistory( + modelName: "MigrationDemo", + migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"] + ) ) ] @@ -210,17 +216,14 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD private func selectModelVersion(_ model: ModelMetadata) { - if self.dataStack?.modelVersion == model.version { + if self.dataStack?.modelVersion == model.schemaHistory.currentModelVersion { return } self.set(dataStack: nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack - let dataStack = DataStack( - modelName: "MigrationDemo", - migrationChain: model.migrationChain - ) + let dataStack = DataStack(schemaHistory: model.schemaHistory) self.setEnabled(false) let progress = dataStack.addStorage( @@ -313,7 +316,13 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD if let dataStack = dataStack, let model = model { - self.segmentedControl?.selectedSegmentIndex = self.models.map { $0.version }.index(of: model.version)! + self.segmentedControl?.selectedSegmentIndex = self.models + .index( + where: { (_, _, schemaHistory) -> Bool in + + schemaHistory.currentModelVersion == model.schemaHistory.currentModelVersion + } + )! self._dataStack = dataStack let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.descending("dna"))) diff --git a/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift b/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift index 513ed02..4b21375 100644 --- a/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift +++ b/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift @@ -17,7 +17,7 @@ import CoreStore class BaseTestDataTestCase: BaseTestCase { @nonobjc - let dateFormatter: DateFormatter = { + let dateFormatter: DateFormatter = cs_lazy { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") @@ -25,7 +25,7 @@ class BaseTestDataTestCase: BaseTestCase { formatter.calendar = Calendar(identifier: Calendar.Identifier.gregorian) formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ" return formatter - }() + } @nonobjc func prepareTestDataForStack(_ stack: DataStack, configurations: [ModelConfiguration] = [nil]) { diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index f2369ef..953deed 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -57,8 +57,8 @@ class DynamicModelTests: BaseTestDataTestCase { func testDynamicModels_CanBeDeclaredCorrectly() { let dataStack = DataStack( - dynamicModel: DynamicModel( - version: "V1", + CoreStoreSchema( + modelVersion: "V1", entities: [ Entity("Animal"), Entity("Dog"), diff --git a/CoreStoreTests/ErrorTests.swift b/CoreStoreTests/ErrorTests.swift index ac294fd..ce90851 100644 --- a/CoreStoreTests/ErrorTests.swift +++ b/CoreStoreTests/ErrorTests.swift @@ -85,16 +85,19 @@ final class ErrorTests: XCTestCase { let dummyURL = URL(string: "file:///test1/test2.sqlite")! - let model = NSManagedObjectModel.fromBundle(Bundle(for: type(of: self)), modelName: "Model") + let schemaHistory = SchemaHistory( + modelName: "Model", + bundle: Bundle(for: type(of: self)) + ) let version = "1.0.0" - let error = CoreStoreError.mappingModelNotFound(localStoreURL: dummyURL, targetModel: model, targetModelVersion: version) + let error = CoreStoreError.mappingModelNotFound(localStoreURL: dummyURL, targetModel: schemaHistory.rawModel, targetModelVersion: version) XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain) XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.mappingModelNotFound.rawValue) let userInfo: NSDictionary = [ "localStoreURL": dummyURL, - "targetModel": model, + "targetModel": schemaHistory.rawModel, "targetModelVersion": version ] let objcError = error.bridgeToObjectiveC diff --git a/CoreStoreTests/MigrationChainTests.swift b/CoreStoreTests/MigrationChainTests.swift index 25e2616..284107c 100644 --- a/CoreStoreTests/MigrationChainTests.swift +++ b/CoreStoreTests/MigrationChainTests.swift @@ -37,8 +37,8 @@ final class MigrationChainTests: XCTestCase { dynamic func test_ThatNilMigrationChains_HaveNoVersions() { let chain: MigrationChain = nil - XCTAssertTrue(chain.valid) - XCTAssertTrue(chain.empty) + XCTAssertTrue(chain.isValid) + XCTAssertTrue(chain.isEmpty) XCTAssertFalse(chain.contains("version1")) XCTAssertNil(chain.nextVersionFrom("version1")) @@ -48,8 +48,8 @@ final class MigrationChainTests: XCTestCase { dynamic func test_ThatStringMigrationChains_HaveOneVersion() { let chain: MigrationChain = "version1" - XCTAssertTrue(chain.valid) - XCTAssertTrue(chain.empty) + XCTAssertTrue(chain.isValid) + XCTAssertTrue(chain.isEmpty) XCTAssertTrue(chain.contains("version1")) XCTAssertFalse(chain.contains("version2")) @@ -62,8 +62,8 @@ final class MigrationChainTests: XCTestCase { dynamic func test_ThatArrayMigrationChains_HaveLinearVersions() { let chain: MigrationChain = ["version1", "version2", "version3", "version4"] - XCTAssertTrue(chain.valid) - XCTAssertFalse(chain.empty) + XCTAssertTrue(chain.isValid) + XCTAssertFalse(chain.isEmpty) XCTAssertTrue(chain.contains("version1")) XCTAssertTrue(chain.contains("version2")) @@ -86,8 +86,8 @@ final class MigrationChainTests: XCTestCase { "version2": "version3", "version3": "version4" ] - XCTAssertTrue(chain.valid) - XCTAssertFalse(chain.empty) + XCTAssertTrue(chain.isValid) + XCTAssertFalse(chain.isEmpty) XCTAssertTrue(chain.contains("version1")) XCTAssertTrue(chain.contains("version2")) diff --git a/CoreStoreTests/SetupTests.swift b/CoreStoreTests/SetupTests.swift index 4be0452..44bfd22 100644 --- a/CoreStoreTests/SetupTests.swift +++ b/CoreStoreTests/SetupTests.swift @@ -36,10 +36,13 @@ class SetupTests: BaseTestDataTestCase { do { - let model = NSManagedObjectModel.mergedModel(from: [Bundle(for: type(of: self))])! - - let stack = DataStack(model: model, migrationChain: nil) - XCTAssertEqual(stack.coordinator.managedObjectModel, model) + let schemaHistory = SchemaHistory( + modelName: "Model", + bundle: Bundle(for: type(of: self)), + migrationChain: nil + ) + let stack = DataStack(schemaHistory: schemaHistory) + XCTAssertEqual(stack.coordinator.managedObjectModel, schemaHistory.rawModel) XCTAssertEqual(stack.rootSavingContext.persistentStoreCoordinator, stack.coordinator) XCTAssertNil(stack.rootSavingContext.parent) XCTAssertFalse(stack.rootSavingContext.isDataStackContext) @@ -47,18 +50,18 @@ class SetupTests: BaseTestDataTestCase { XCTAssertEqual(stack.mainContext.parent, stack.rootSavingContext) XCTAssertTrue(stack.mainContext.isDataStackContext) XCTAssertFalse(stack.mainContext.isTransactionContext) - XCTAssertEqual(stack.model, model) - XCTAssertTrue(stack.migrationChain.valid) - XCTAssertTrue(stack.migrationChain.empty) - XCTAssertTrue(stack.migrationChain.rootVersions.isEmpty) - XCTAssertTrue(stack.migrationChain.leafVersions.isEmpty) + XCTAssertEqual(stack.schemaHistory.rawModel, schemaHistory.rawModel) + XCTAssertTrue(stack.schemaHistory.migrationChain.isValid) + XCTAssertTrue(stack.schemaHistory.migrationChain.isEmpty) + XCTAssertTrue(stack.schemaHistory.migrationChain.rootVersions.isEmpty) + XCTAssertTrue(stack.schemaHistory.migrationChain.leafVersions.isEmpty) CoreStore.defaultStack = stack XCTAssertEqual(CoreStore.defaultStack, stack) } do { - let migrationChain: MigrationChain = ["version1", "version2", "version3"] + let migrationChain: MigrationChain = ["version1", "version2", "version3", "Model"] let stack = self.expectLogger([.logWarning]) { @@ -69,7 +72,7 @@ class SetupTests: BaseTestDataTestCase { ) } XCTAssertEqual(stack.modelVersion, "Model") - XCTAssertEqual(stack.migrationChain, migrationChain) + XCTAssertEqual(stack.schemaHistory.migrationChain, migrationChain) CoreStore.defaultStack = stack XCTAssertEqual(CoreStore.defaultStack, stack) @@ -226,7 +229,10 @@ class SetupTests: BaseTestDataTestCase { modelName: "Model", bundle: Bundle(for: type(of: self)) ) - try sqliteStore.eraseStorageAndWait(metadata: metadata, soureModelHint: stack.model[metadata]) + try sqliteStore.eraseStorageAndWait( + metadata: metadata, + soureModelHint: stack.schemaHistory.schema(for: metadata)?.rawModel() + ) XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal"))) XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm"))) @@ -344,7 +350,10 @@ class SetupTests: BaseTestDataTestCase { modelName: "Model", bundle: Bundle(for: type(of: self)) ) - try sqliteStore.eraseStorageAndWait(metadata: metadata, soureModelHint: stack.model[metadata]) + try sqliteStore.eraseStorageAndWait( + metadata: metadata, + soureModelHint: stack.schemaHistory.schema(for: metadata)?.rawModel() + ) XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal"))) XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm"))) diff --git a/Sources/CoreStoreError.swift b/Sources/CoreStoreError.swift index fc7a79b..f32a670 100644 --- a/Sources/CoreStoreError.swift +++ b/Sources/CoreStoreError.swift @@ -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 } } diff --git a/Sources/CoreStoreStrings.swift b/Sources/CoreStoreStrings.swift index 5dbc3db..617ac54 100644 --- a/Sources/CoreStoreStrings.swift +++ b/Sources/CoreStoreStrings.swift @@ -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 diff --git a/Sources/Importing/ImportableAttributeType.swift b/Sources/Importing/ImportableAttributeType.swift index dc026e9..3a10226 100644 --- a/Sources/Importing/ImportableAttributeType.swift +++ b/Sources/Importing/ImportableAttributeType.swift @@ -580,11 +580,11 @@ extension UUID: ImportableAttributeType { enum Static { - static let empty: UUID = { + static let empty: UUID = cs_lazy { var zero = Array(repeating: 0, count: 16) return NSUUID(uuidBytes: &zero) as UUID - }() + } } return Static.empty } diff --git a/Sources/Internal/EntityIdentifier.swift b/Sources/Internal/EntityIdentifier.swift new file mode 100644 index 0000000..a12b0b0 --- /dev/null +++ b/Sources/Internal/EntityIdentifier.swift @@ -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 + } +} diff --git a/Sources/Internal/Functions.swift b/Sources/Internal/Functions.swift index e7191bc..2500fa0 100644 --- a/Sources/Internal/Functions.swift +++ b/Sources/Internal/Functions.swift @@ -100,3 +100,12 @@ internal func cs_typeName(_ name: String?) -> String { return "<\(name ?? "unknown")>" } + + +// MARK: Functional + +@inline(__always) +internal func cs_lazy(_ closure: () -> T) -> T { + + return closure() +} diff --git a/Sources/Internal/NSEntityDescription+DynamicModel.swift b/Sources/Internal/NSEntityDescription+DynamicModel.swift new file mode 100644 index 0000000..b5d91d8 --- /dev/null +++ b/Sources/Internal/NSEntityDescription+DynamicModel.swift @@ -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" + } +} diff --git a/Sources/Internal/NSManagedObjectModel+Setup.swift b/Sources/Internal/NSManagedObjectModel+Setup.swift deleted file mode 100644 index 9ac892e..0000000 --- a/Sources/Internal/NSManagedObjectModel+Setup.swift +++ /dev/null @@ -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 = []) -> 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 ?? "")\". 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? { - - get { - - let value: NSSet? = cs_getAssociatedObjectForKey( - &PropertyKeys.modelVersions, - inObject: self - ) - return value as? Set - } - 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? - } -} diff --git a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift index ad6f6fe..b2b053f 100644 --- a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift @@ -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 "" } @@ -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)\"" + } +} diff --git a/Sources/Migrating/DataStack+Migration.swift b/Sources/Migrating/DataStack+Migration.swift index 07d1a6c..56cc107 100644 --- a/Sources/Migrating/DataStack+Migration.swift +++ b/Sources/Migrating/DataStack+Migration.swift @@ -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(_ 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) } } } diff --git a/Sources/Migrating/MigrationChain.swift b/Sources/Migrating/MigrationChain.swift index 9507303..a583e52 100644 --- a/Sources/Migrating/MigrationChain.swift +++ b/Sources/Migrating/MigrationChain.swift @@ -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 internal let leafVersions: Set - internal let valid: Bool + internal let isValid: Bool - internal var empty: Bool { + internal var isEmpty: Bool { return self.versionTree.count <= 0 } diff --git a/Sources/ObjectiveC/CSDataStack.swift b/Sources/ObjectiveC/CSDataStack.swift index f8bacdc..aca930b 100644 --- a/Sources/ObjectiveC/CSDataStack.swift +++ b/Sources/ObjectiveC/CSDataStack.swift @@ -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. */ diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index afe8842..eb42b7a 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -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. */ diff --git a/Sources/Setup/Dynamic Models/DynamicModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift similarity index 81% rename from Sources/Setup/Dynamic Models/DynamicModel.swift rename to Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift index 3ede3c0..ebeaefa 100644 --- a/Sources/Setup/Dynamic Models/DynamicModel.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift @@ -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] = [:] 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] + + + // MARK: Private + + private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue") + + private let allEntities: Set + + 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 - private let entitiesByConfiguration: [String: Set] -} - - -// MARK: - ModelCache - -fileprivate enum ModelCache { - - fileprivate static func performUpdate(_ 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] = [:] } diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift new file mode 100644 index 0000000..f18123a --- /dev/null +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift @@ -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 +} diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift new file mode 100644 index 0000000..741d5f8 --- /dev/null +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift @@ -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 +} diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift new file mode 100644 index 0000000..94f1cc2 --- /dev/null +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift @@ -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? +} diff --git a/Sources/Setup/Dynamic Models/Entity.swift b/Sources/Setup/Dynamic Models/Entity.swift index 7ec9882..b73c099 100644 --- a/Sources/Setup/Dynamic Models/Entity.swift +++ b/Sources/Setup/Dynamic Models/Entity.swift @@ -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: EntityProtocol { +public struct Entity: DynamicEntity, Hashable { public init(_ entityName: String) { @@ -47,136 +47,54 @@ public struct Entity: 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 } } diff --git a/Sources/Setup/Dynamic Models/SchemaHistory.swift b/Sources/Setup/Dynamic Models/SchemaHistory.swift new file mode 100644 index 0000000..70de026 --- /dev/null +++ b/Sources/Setup/Dynamic Models/SchemaHistory.swift @@ -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 ?? "")\". 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() }) + } +} diff --git a/Sources/Setup/StorageInterfaces/LegacySQLiteStore.swift b/Sources/Setup/StorageInterfaces/LegacySQLiteStore.swift index f6b4c70..db8f1e9 100644 --- a/Sources/Setup/StorageInterfaces/LegacySQLiteStore.swift +++ b/Sources/Setup/StorageInterfaces/LegacySQLiteStore.swift @@ -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) diff --git a/Sources/Setup/StorageInterfaces/SQLiteStore.swift b/Sources/Setup/StorageInterfaces/SQLiteStore.swift index 510d95f..b386e72 100644 --- a/Sources/Setup/StorageInterfaces/SQLiteStore.swift +++ b/Sources/Setup/StorageInterfaces/SQLiteStore.swift @@ -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(