diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 7df750f..79e1e7f 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -214,14 +214,14 @@ 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 */; }; + B52F74411E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74421E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74431E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74441E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */; }; + B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; + B52F74461E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; + B52F74471E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */; }; + B52F74481E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.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 */; }; @@ -406,6 +406,10 @@ B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B58B22F51C93C1BA00521925 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A53019C5C6DA005002A5 /* CoreStore.framework */; }; + B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; + B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; + B58D0C651EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; + B58D0C661EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; }; B596BBB21DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; B596BBB31DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; B596BBB41DD5A016001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; @@ -434,6 +438,10 @@ B5A991ED1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */; }; B5A991EE1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */; }; B5A991EF1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */; }; + B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; + B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; + B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; + B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; }; B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; }; B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; }; B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; }; @@ -464,10 +472,10 @@ B5D339E81E9493A500C880DE /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339E61E9493A500C880DE /* Entity.swift */; }; B5D339E91E9493A500C880DE /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339E61E9493A500C880DE /* Entity.swift */; }; B5D339EA1E9493A500C880DE /* Entity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339E61E9493A500C880DE /* Entity.swift */; }; - B5D339EC1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; - B5D339ED1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; - B5D339EE1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; - B5D339EF1E9495E500C880DE /* Attribute+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */; }; + B5D339EC1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; + B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; + B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; + B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */; }; B5D339F11E94AF5800C880DE /* CoreStoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */; }; B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */; }; B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */; }; @@ -527,6 +535,14 @@ B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */; }; B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */; }; B5E2222E1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */; }; + B5E41EBB1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EBC1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EBD1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EBE1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */; }; + B5E41EC01EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; + B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; + B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; + B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; }; B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; @@ -698,8 +714,8 @@ 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 = ""; }; + B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyXcodeDataModelSchema.swift; sourceTree = ""; }; + B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeDataModelSchema.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 = ""; }; @@ -749,6 +765,7 @@ B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = ""; }; B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = ""; }; + B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+DynamicModel.swift"; sourceTree = ""; }; B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvenienceTests.swift; sourceTree = ""; }; B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchableSource.swift; sourceTree = ""; }; B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryableSource.swift; sourceTree = ""; }; @@ -757,6 +774,7 @@ B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = ""; }; B5A5F2651CAEC50F004AB9AF /* CSSelect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSelect.swift; sourceTree = ""; }; B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionLock.swift; sourceTree = ""; }; + B5A9921E1EA898710091A2E3 /* UserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; B5AD60CD1C90141E00F2B2E8 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; }; B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = ""; }; B5BDC91A1C202269008147CD /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cartfile; path = ../Cartfile; sourceTree = ""; }; @@ -769,7 +787,7 @@ B5D339DC1E9489C700C880DE /* DynamicObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicObject.swift; sourceTree = ""; }; B5D339E11E948C3600C880DE /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; B5D339E61E9493A500C880DE /* Entity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Entity.swift; sourceTree = ""; }; - B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Attribute+Querying.swift"; sourceTree = ""; }; + B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Querying.swift"; sourceTree = ""; }; B5D339F01E94AF5800C880DE /* CoreStoreStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreStrings.swift; sourceTree = ""; }; B5D33A001E96012400C880DE /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = ""; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; @@ -791,6 +809,8 @@ B5E1B5A71CAA49E2007FD580 /* CSDataStack+Migrating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSDataStack+Migrating.swift"; sourceTree = ""; }; B5E222221CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSynchronousDataTransaction.swift; sourceTree = ""; }; B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSUnsafeDataTransaction.swift; sourceTree = ""; }; + B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationMappingProvider.swift; sourceTree = ""; }; + B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DynamicSchema+Convenience.swift"; sourceTree = ""; }; B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = ""; }; B5E834BA1B7691F3001D3D50 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; @@ -1047,8 +1067,8 @@ children = ( B52F743A1E9B8724005F3DAC /* DynamicSchema.swift */, B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */, - B52F743C1E9B8724005F3DAC /* XcodeDataModel.swift */, - B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModel.swift */, + B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */, + B52F743B1E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift */, ); path = "Dynamic Schema"; sourceTree = ""; @@ -1125,6 +1145,7 @@ B56007151B4018AB00A9A8F9 /* MigrationChain.swift */, B5A261201B64BFDB006EB6D3 /* MigrationType.swift */, B56965231B356B820075EE4A /* MigrationResult.swift */, + B5E41EB91EA8C3A1006240F0 /* Migration Mapping Providers */, ); path = Migrating; sourceTree = ""; @@ -1215,6 +1236,14 @@ name = "Fetching and Querying"; sourceTree = ""; }; + B5E41EB91EA8C3A1006240F0 /* Migration Mapping Providers */ = { + isa = PBXGroup; + children = ( + B5E41EBA1EA8C3B7006240F0 /* MigrationMappingProvider.swift */, + ); + path = "Migration Mapping Providers"; + sourceTree = ""; + }; B5E834B61B7630BD001D3D50 /* Importing */ = { isa = PBXGroup; children = ( @@ -1267,7 +1296,7 @@ B5E84EFD1AFF847B0064E85B /* Fetching and Querying */ = { isa = PBXGroup; children = ( - B5D339EB1E9495E500C880DE /* Attribute+Querying.swift */, + B5D339EB1E9495E500C880DE /* CoreStoreObject+Querying.swift */, B5E84EFE1AFF847B0064E85B /* BaseDataTransaction+Querying.swift */, B5E84F061AFF847B0064E85B /* DataStack+Querying.swift */, B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */, @@ -1323,6 +1352,8 @@ B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */, B5FAD6A81B50A4B300714891 /* Progress+Convenience.swift */, B5202CF91C04688100DED140 /* NSFetchedResultsController+Convenience.swift */, + B5A9921E1EA898710091A2E3 /* UserInfo.swift */, + B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */, ); path = Convenience; sourceTree = ""; @@ -1340,6 +1371,7 @@ B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */, B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */, B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */, + B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */, B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */, B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */, B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */, @@ -1690,8 +1722,9 @@ B5E1B5981CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A5F1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AA1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74411E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74411E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */, B54A6A551BA15F2A007870FD /* FetchedResultsControllerDelegate.swift in Sources */, B5D339E21E948C3600C880DE /* Value.swift in Sources */, B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */, @@ -1738,12 +1771,13 @@ B56965241B356B820075EE4A /* MigrationResult.swift in Sources */, B5FE4DAC1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B501FDE71CA8D20500BE22EF /* CSListObserver.swift in Sources */, + B5E41EC01EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B501FDE21CA8D1F500BE22EF /* CSListMonitor.swift in Sources */, 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */, B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */, B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */, B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, - B52F74451E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */, @@ -1762,7 +1796,7 @@ B52F742F1E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */, B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */, - B5D339EC1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339EC1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B5E84F0F1AFF847B0064E85B /* From.swift in Sources */, B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */, B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */, @@ -1774,6 +1808,7 @@ B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */, B5D33A011E96012400C880DE /* Relationship.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, + B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DB1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B559CD491CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, B5ECDC2F1CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, @@ -1794,6 +1829,7 @@ B5A991EC1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5FE4DA71C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBB1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, B5ECDBEC1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, @@ -1862,8 +1898,9 @@ B5E1B59A1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A601CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74421E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74421E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B51FE5AD1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */, B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B5D339E31E948C3600C880DE /* Value.swift in Sources */, 82BA18C51C4BBD5300A0916E /* ListObserver.swift in Sources */, @@ -1910,12 +1947,13 @@ 82BA18D41C4BBD7100A0916E /* NSManagedObjectContext+Querying.swift in Sources */, 82BA18D51C4BBD7100A0916E /* NSManagedObjectContext+Setup.swift in Sources */, B501FDE91CA8D20500BE22EF /* CSListObserver.swift in Sources */, + B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B501FDE41CA8D1F500BE22EF /* CSListMonitor.swift in Sources */, B5FE4DA31C8481E100FA6A91 /* StorageInterface.swift in Sources */, B5ECDC131CA816E500C7F112 /* CSTweak.swift in Sources */, 82BA18C91C4BBD5900A0916E /* MigrationType.swift in Sources */, 82BA18D01C4BBD7100A0916E /* MigrationManager.swift in Sources */, - B52F74461E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74461E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, 82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */, B59851491C90289D00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E1B5A41CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, @@ -1933,7 +1971,7 @@ B52F74301E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, 82BA18C31C4BBD5300A0916E /* ObjectObserver.swift in Sources */, 82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */, - B5D339ED1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, 82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */, 82BA18C71C4BBD5900A0916E /* CoreStore+Migration.swift in Sources */, B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, @@ -1946,6 +1984,7 @@ 82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */, B5D33A021E96012400C880DE /* Relationship.swift in Sources */, B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, + B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DC1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5ECDC311CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, 82BA18AF1C4BBD3100A0916E /* CoreStore+Transaction.swift in Sources */, @@ -1966,6 +2005,7 @@ B5A991ED1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5ECDBEE1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBC1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, 82BA18D61C4BBD7100A0916E /* NSManagedObjectContext+Transaction.swift in Sources */, 82BA18B91C4BBD4A00A0916E /* From.swift in Sources */, B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */, @@ -2034,8 +2074,9 @@ B5ECDC211CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B52DD1C21BE1F94600949AFE /* MigrationManager.swift in Sources */, B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74441E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74441E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, + B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */, B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */, B5D339E51E948C3600C880DE /* Value.swift in Sources */, B5A5F26A1CAEC50F004AB9AF /* CSSelect.swift in Sources */, @@ -2082,12 +2123,13 @@ B52DD19D1BE1F92C00949AFE /* BaseDataTransaction.swift in Sources */, B5220E131D1305ED009BC71E /* SectionBy.swift in Sources */, B559CD4D1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, + B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B5ECDBE91CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */, B52DD1B81BE1F94000949AFE /* DataStack+Migration.swift in Sources */, B5ECDC091CA8138100C7F112 /* CSOrderBy.swift in Sources */, B52DD1A51BE1F92F00949AFE /* ImportableUniqueObject.swift in Sources */, B5E222271CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, - B52F74481E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74481E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52DD19C1BE1F92C00949AFE /* Into.swift in Sources */, B5FE4DA51C8481E100FA6A91 /* StorageInterface.swift in Sources */, @@ -2105,7 +2147,7 @@ B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B52DD1A31BE1F92C00949AFE /* SaveResult.swift in Sources */, B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */, - B5D339EF1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */, B52DD1CB1BE1F94600949AFE /* WeakObject.swift in Sources */, B52DD1C11BE1F94600949AFE /* Functions.swift in Sources */, @@ -2118,6 +2160,7 @@ B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, B5D33A041E96012400C880DE /* Relationship.swift in Sources */, B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */, + B58D0C661EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DE1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5220E141D130614009BC71E /* DataStack+Observing.swift in Sources */, B52DD1A21BE1F92C00949AFE /* CoreStore+Transaction.swift in Sources */, @@ -2138,6 +2181,7 @@ B5A991EF1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5220E201D130813009BC71E /* CSObjectMonitor.swift in Sources */, B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBE1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, B5220E171D1306DF009BC71E /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA081CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */, @@ -2206,8 +2250,9 @@ B5519A611CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B5FE4DAE1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, - B52F74431E9B8724005F3DAC /* LegacyXcodeDataModel.swift in Sources */, + B52F74431E9B8724005F3DAC /* LegacyXcodeDataModelSchema.swift in Sources */, B51FE5AE1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */, B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */, B5D339E41E948C3600C880DE /* Value.swift in Sources */, B53FBA0E1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, @@ -2254,12 +2299,13 @@ B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */, B501FDEA1CA8D20500BE22EF /* CSListObserver.swift in Sources */, B501FDE51CA8D1F500BE22EF /* CSListMonitor.swift in Sources */, + B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */, B5ECDC141CA816E500C7F112 /* CSTweak.swift in Sources */, B56321AE1BD6521C006C9394 /* NotificationObserver.swift in Sources */, B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */, B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */, B598514A1C90289E00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, - B52F74471E9B8724005F3DAC /* XcodeDataModel.swift in Sources */, + B52F74471E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5FEC1901C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */, B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */, B5E1B5A51CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, @@ -2277,7 +2323,7 @@ B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */, B56321991BD65216006C9394 /* OrderBy.swift in Sources */, - B5D339EE1E9495E500C880DE /* Attribute+Querying.swift in Sources */, + B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */, B563218E1BD65216006C9394 /* SaveResult.swift in Sources */, B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, @@ -2290,6 +2336,7 @@ B56321B61BD6521C006C9394 /* WeakObject.swift in Sources */, B5D33A031E96012400C880DE /* Relationship.swift in Sources */, B559CD4C1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, + B58D0C651EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DD1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5ECDC321CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, B56321AC1BD6521C006C9394 /* Functions.swift in Sources */, @@ -2310,6 +2357,7 @@ B5A991EE1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */, B5ECDBEF1CA6BF2000C7F112 /* CSFrom.swift in Sources */, B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, + B5E41EBD1EA8C3B7006240F0 /* MigrationMappingProvider.swift in Sources */, B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */, B56321861BD65216006C9394 /* CoreStoreLogger.swift in Sources */, B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */, diff --git a/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift b/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift index 2e0af90..59d7571 100644 --- a/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift +++ b/CoreStoreDemo/CoreStoreDemo/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit +import CoreStore // MARK: - AppDelegate @@ -22,6 +23,47 @@ class AppDelegate: UIResponder, UIApplicationDelegate { application.statusBarStyle = .lightContent + /// Generated by CoreStore on 4/21/17, 2:41 PM + class Place: CoreStoreObject { + + let latitude = Value.Optional("latitude", default: 0.0) + let title = Value.Optional("title") + let longitude = Value.Optional("longitude", default: 0.0) + let subtitle = Value.Optional("subtitle") + } + class Palette: CoreStoreObject { + + let saturation = Value.Optional("saturation", default: 0.0) + let hue = Value.Optional("hue", default: 0) + let brightness = Value.Optional("brightness", default: 0.0) + let colorName = Value.Optional("colorName", isTransient: true) + } + class TimeZone: CoreStoreObject { + + let secondsFromGMT = Value.Optional("secondsFromGMT", default: 0) + let name = Value.Optional("name") + let daylightSavingTimeOffset = Value.Optional("daylightSavingTimeOffset", default: 0.0) + let abbreviation = Value.Optional("abbreviation") + let hasDaylightSavingTime = Value.Optional("hasDaylightSavingTime") + } + + + + let schema = CoreStoreSchema( + modelVersion: "CoreStoreDemo", + entities: [ + Entity("Place"), + Entity("Palette"), + Entity("TimeZone"), + ], + versionLock: [ + "Place": [0x25cb5bd001887b92, 0xfe86dd433a5e0430, 0xcca50ac3f3659b68, 0xfe4e494ff66439b0], + "Palette": [0xa306515d026d3c43, 0x1b299716733e56f6, 0x53bff8954221a1b6, 0xa74d6b1e613923ab], + "TimeZone": [0x92e08db969e46163, 0xae9cf1ab738868c5, 0xb6a269249771a562, 0x58a357eab4c99ed5] + ] + ) + + print(schema.printCoreStoreSchema()) return true } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index 0b2d310..27f5fab 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -43,7 +43,10 @@ private struct Static { didSet { - self.palettes.refetch(self.filter.whereClause()) + self.palettes.refetch( + self.filter.whereClause(), + OrderBy(.ascending(#keyPath(Palette.hue))) + ) } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift index 74e4888..fffc643 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/Palette.swift @@ -49,7 +49,7 @@ class Palette: NSManagedObject { } set { - self.setValue(newValue, forKvcKey: #keyPath(Palette.colorName)) + self.setValue(newValue.cs_toImportableNativeType(), forKvcKey: #keyPath(Palette.colorName)) } } diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index bb4f994..e138e48 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -45,7 +45,13 @@ class Dog: Animal { class Person: CoreStoreObject { - let name = Value.Required("name") + let name = Value.Required( + "name", + customGetter: { (`self`, getValue) in + + return "Mr. \(getValue())" + } + ) let pet = Relationship.ToOne("pet", inverse: { $0.master }) } @@ -108,6 +114,9 @@ class DynamicModelTests: BaseTestDataTestCase { let person = transaction.create(Into()) XCTAssertNil(person.pet.value) + person.name .= "John" + XCTAssertEqual(person.name.value, "Mr. John") // Custom getter + person.pet .= dog XCTAssertEqual(person.pet.value, dog) XCTAssertEqual(person.pet.value?.master.value, person) diff --git a/CoreStoreTests/SetupTests.swift b/CoreStoreTests/SetupTests.swift index 44bfd22..2a7ee7d 100644 --- a/CoreStoreTests/SetupTests.swift +++ b/CoreStoreTests/SetupTests.swift @@ -213,7 +213,6 @@ class SetupTests: BaseTestDataTestCase { self.prepareTestDataForStack(stack) } XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) - XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal"))) XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm"))) return try NSPersistentStoreCoordinator.metadataForPersistentStore( @@ -334,7 +333,6 @@ class SetupTests: BaseTestDataTestCase { self.prepareTestDataForStack(stack) } XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path)) - XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal"))) XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm"))) return try NSPersistentStoreCoordinator.metadataForPersistentStore( diff --git a/Sources/Convenience/DynamicSchema+Convenience.swift b/Sources/Convenience/DynamicSchema+Convenience.swift new file mode 100644 index 0000000..e7f4b30 --- /dev/null +++ b/Sources/Convenience/DynamicSchema+Convenience.swift @@ -0,0 +1,287 @@ +// +// DynamicSchema+Convenience.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 extension DynamicSchema { + + public func printCoreStoreSchema() -> String { + + let model = self.rawModel() + let entitiesByName = model.entitiesByName + + var output = "/// Generated by CoreStore on \(DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .short))\n" + var addedInverse: Set = [] + for (entityName, entity) in entitiesByName { + + let superName: String + if let superEntity = entity.superentity { + + superName = superEntity.name! + } + else { + + superName = String(describing: CoreStoreObject.self) + } + output.append("class \(entityName): \(superName) {\n") + defer { + + output.append("}\n") + } + + let attributesByName = entity.attributesByName + if !attributesByName.isEmpty { + + output.append(" \n") + for (attributeName, attribute) in attributesByName { + + let containerType: String + if attribute.attributeType == .transformableAttributeType { + + if attribute.isOptional { + + containerType = "Transformable.Optional" + } + else { + + containerType = "Transformable.Required" + } + } + else { + + if attribute.isOptional { + + containerType = "Value.Optional" + } + else { + + containerType = "Value.Required" + } + } + let valueType: Any.Type + var defaultString = "" + switch attribute.attributeType { + + case .integer16AttributeType: + valueType = Int16.self + if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType), + defaultValue != Int16.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .integer32AttributeType: + valueType = Int32.self + if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType), + defaultValue != Int32.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .integer64AttributeType: + valueType = Int64.self + if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType), + defaultValue != Int64.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .decimalAttributeType: + valueType = NSDecimalNumber.self + if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType), + defaultValue != NSDecimalNumber.cs_emptyValue() { + + defaultString = ", default: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")" + } + case .doubleAttributeType: + valueType = Double.self + if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType), + defaultValue != Double.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .floatAttributeType: + valueType = Float.self + if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType), + defaultValue != Float.cs_emptyValue() { + + defaultString = ", default: \(defaultValue)" + } + case .stringAttributeType: + valueType = String.self + if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType), + defaultValue != String.cs_emptyValue() { + + // TODO: escape strings + defaultString = ", default: \"\(defaultValue)\"" + } + case .booleanAttributeType: + valueType = Bool.self + if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType), + defaultValue != Bool.cs_emptyValue() { + + defaultString = ", default: \(defaultValue ? "true" : "false")" + } + case .dateAttributeType: + valueType = Date.self + if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType), + defaultValue != Date.cs_emptyValue() { + + defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))" + } + case .binaryDataAttributeType: + valueType = Data.self + if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType), + defaultValue != Data.cs_emptyValue() { + + let count = defaultValue.count + let bytes = defaultValue.withUnsafeBytes { (pointer: UnsafePointer) in + + return (0 ..< (count / MemoryLayout.size)) + .map({ "\("0x\(String(pointer[$0], radix: 16, uppercase: false))")" }) + } + defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])" + } + case .transformableAttributeType: + if let attributeValueClassName = attribute.attributeValueClassName { + + valueType = NSClassFromString(attributeValueClassName)! + } + else { + + valueType = (NSCoding & NSCopying).self + } + if let defaultValue = attribute.defaultValue { + + defaultString = ", default: /* \"\(defaultValue)\" */" + } + else if !attribute.isOptional { + + defaultString = ", default: /* required */" + } + default: + fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)") + } + let indexedString = attribute.isIndexed ? ", isIndexed: true" : "" + let transientString = attribute.isTransient ? ", isTransient: true" : "" + // TODO: escape strings + let versionHashModifierString = attribute.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? "" + // TODO: escape strings + let renamingIdentifierString = attribute.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? "" + output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n") + } + } + + let relationshipsByName = entity.relationshipsByName + if !relationshipsByName.isEmpty { + + output.append(" \n") + for (relationshipName, relationship) in relationshipsByName { + + let containerType: String + var minCountString = "" + var maxCountString = "" + if relationship.isToMany { + + let minCount = relationship.minCount + let maxCount = relationship.maxCount + if relationship.isOrdered { + + containerType = "Relationship.ToManyOrdered" + } + else { + + containerType = "Relationship.ToManyUnordered" + } + if minCount > 0 { + + minCountString = ", minCount: \(minCount)" + } + if maxCount > 0 { + + maxCountString = ", maxCount: \(maxCount)" + } + } + else { + + containerType = "Relationship.ToOne" + } + var inverseString = "" + let relationshipQualifier = "\(entityName).\(relationshipName)" + if !addedInverse.contains(relationshipQualifier), + let inverseRelationship = relationship.inverseRelationship { + + inverseString = ", inverse: { $0.\(inverseRelationship.name) }" + addedInverse.insert("\(relationship.destinationEntity!.name!).\(inverseRelationship.name)") + } + var deleteRuleString = "" + if relationship.deleteRule != .nullifyDeleteRule { + + switch relationship.deleteRule { + + case .cascadeDeleteRule: + deleteRuleString = ", deleteRule: .cascade" + + case .denyDeleteRule: + deleteRuleString = ", deleteRule: .deny" + + case .nullifyDeleteRule: + deleteRuleString = ", deleteRule: .nullify" + + default: + fatalError("Unsupported delete rule \((relationship.deleteRule)) for relationship \"\(relationshipQualifier)\"") + } + } + let versionHashModifierString = relationship.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? "" + let renamingIdentifierString = relationship.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? "" + output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n") + } + } + } + output.append("\n\n\n") + output.append("CoreStoreSchema(\n") + output.append(" modelVersion: \"\(self.modelVersion)\",\n") + output.append(" entities: [\n") + for (entityName, entity) in entitiesByName { + + var abstractString = "" + if entity.isAbstract { + + abstractString = ", isAbstract: true" + } + var versionHashModifierString = "" + if let versionHashModifier = entity.versionHashModifier { + + versionHashModifierString = ", versionHashModifier: \"\(versionHashModifier)\"" + } + output.append(" Entity<\(entityName)>(\"\(entityName)\"\(abstractString)\(versionHashModifierString)),\n") + } + output.append(" ],\n") + output.append(" versionLock: \(VersionLock(entityVersionHashesByName: model.entityVersionHashesByName).description.components(separatedBy: "\n").joined(separator: "\n "))\n") + output.append(")\n\n") + return output + } +} diff --git a/Sources/Convenience/NSManagedObject+Convenience.swift b/Sources/Convenience/NSManagedObject+Convenience.swift index fa60a9e..d3c273c 100644 --- a/Sources/Convenience/NSManagedObject+Convenience.swift +++ b/Sources/Convenience/NSManagedObject+Convenience.swift @@ -112,8 +112,7 @@ public extension NSManagedObject { } @nonobjc @inline(__always) - @discardableResult - public func setValue(_ value: Any?, forKvcKey KVCKey: KeyPath) -> Any? { + public func setValue(_ value: Any?, forKvcKey KVCKey: KeyPath) { self.willChangeValue(forKey: KVCKey) defer { @@ -121,12 +120,10 @@ public extension NSManagedObject { self.didChangeValue(forKey: KVCKey) } self.setPrimitiveValue(value, forKey: KVCKey) - return value } @nonobjc @inline(__always) - @discardableResult - public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?) rethrows -> T { + public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?) rethrows { self.willChangeValue(forKey: KVCKey) defer { @@ -134,20 +131,6 @@ public extension NSManagedObject { self.didChangeValue(forKey: KVCKey) } self.setPrimitiveValue(try willSetValue(value), forKey: KVCKey) - return value - } - - @nonobjc @inline(__always) - @discardableResult - public func setValue(_ value: T, forKvcKey KVCKey: KeyPath, willSetValue: (T) throws -> Any?, didSetValue: (T) -> T = { $0 }) rethrows -> T { - - self.willChangeValue(forKey: KVCKey) - defer { - - self.didChangeValue(forKey: KVCKey) - } - self.setPrimitiveValue(try willSetValue(value), forKey: KVCKey) - return didSetValue(value) } /** diff --git a/Sources/Convenience/UserInfo.swift b/Sources/Convenience/UserInfo.swift new file mode 100644 index 0000000..389c7cf --- /dev/null +++ b/Sources/Convenience/UserInfo.swift @@ -0,0 +1,115 @@ +// +// UserInfo.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 Foundation + + +// MARK: UserInfo + +/** + The `UserInfo` class is provided by several CoreStore types such as `DataStack`, `ListMonitor`, `ObjectMonitor` and transactions to allow external libraries or user apps to store their own custom data. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this class to store thread-sensitive data. + */ +public final class UserInfo { + + /** + Allows external libraries to store custom data. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter key: the key for custom data. Make sure this is a static pointer that will never be changed. + */ + public subscript(key: UnsafeRawPointer) -> Any? { + + get { + + self.lock.lock() + defer { + + self.lock.unlock() + } + return self.data[key] + } + set { + + self.lock.lock() + defer { + + self.lock.unlock() + } + self.data[key] = newValue + } + } + + /** + Allows external libraries to store custom data in the `DataStack`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey, lazyInit: { MyObject() }] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + - parameter key: the key for custom data. Make sure this is a static pointer that will never be changed. + - parameter lazyInit: a closure to use to lazily-initialize the data + - returns: A custom data identified by `key` + */ + public subscript(key: UnsafeRawPointer, lazyInit closure: () -> Any) -> Any { + + self.lock.lock() + defer { + + self.lock.unlock() + } + if let value = self.data[key] { + + return value + } + let value = closure() + self.data[key] = value + return value + } + + + // MARK: Internal + + internal init() {} + + + // MARK: Private + + private var data: [UnsafeRawPointer: Any] = [:] + private let lock = NSRecursiveLock() +} diff --git a/Sources/CoreStoreStrings.swift b/Sources/CoreStoreStrings.swift index 617ac54..5fc467b 100644 --- a/Sources/CoreStoreStrings.swift +++ b/Sources/CoreStoreStrings.swift @@ -45,7 +45,7 @@ public typealias ModelConfiguration = String? // MARK: - ModelVersion /** - An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension). + An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension). Model version strings don't necessarily have to be numeric or ordered in any way. The migration sequence will always be decided by (or the lack of) the `MigrationChain`. */ public typealias ModelVersion = String diff --git a/Sources/Fetching and Querying/Attribute+Querying.swift b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift similarity index 66% rename from Sources/Fetching and Querying/Attribute+Querying.swift rename to Sources/Fetching and Querying/CoreStoreObject+Querying.swift index ad9f4cc..419457a 100644 --- a/Sources/Fetching and Querying/Attribute+Querying.swift +++ b/Sources/Fetching and Querying/CoreStoreObject+Querying.swift @@ -1,5 +1,5 @@ // -// Attribute+Querying.swift +// CoreStoreObject+Querying.swift // CoreStore // // Copyright © 2017 John Rommel Estropia @@ -27,30 +27,72 @@ import CoreData import Foundation +// MARK: - DynamicObject + +public extension DynamicObject where Self: CoreStoreObject { + + public static func keyPath(_ attribute: (Self) -> ValueContainer.Required) -> String { + + return attribute(self.meta).keyPath + } + + public static func keyPath(_ attribute: (Self) -> ValueContainer.Optional) -> String { + + return attribute(self.meta).keyPath + } + + public static func `where`(_ condition: (Self) -> Where) -> Where { + + return condition(self.meta) + } + + public static func ascending(_ attribute: (Self) -> ValueContainer.Optional) -> OrderBy { + + return OrderBy(.ascending(attribute(self.meta).keyPath)) + } + + public static func descending(_ attribute: (Self) -> ValueContainer.Optional) -> OrderBy { + + return OrderBy(.descending(attribute(self.meta).keyPath)) + } +} + + // MARK: - ValueContainer.Required public extension ValueContainer.Required { + @inline(__always) public static func == (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where(attribute.keyPath, isEqualTo: value) } + + @inline(__always) public static func < (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K < %@", attribute.keyPath, value) } + + @inline(__always) public static func > (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K > %@", attribute.keyPath, value) } + + @inline(__always) public static func <= (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K <= %@", attribute.keyPath, value) } + + @inline(__always) public static func >= (_ attribute: ValueContainer.Required, _ value: V) -> Where { return Where("%K >= %@", attribute.keyPath, value) } + + @inline(__always) public static func != (_ attribute: ValueContainer.Required, _ value: V) -> Where { return !Where(attribute.keyPath, isEqualTo: value) @@ -62,11 +104,13 @@ public extension ValueContainer.Required { public extension ValueContainer.Optional { + @inline(__always) public static func == (_ attribute: ValueContainer.Optional, _ value: V?) -> Where { return Where(attribute.keyPath, isEqualTo: value) } + @inline(__always) public static func != (_ attribute: ValueContainer.Optional, _ value: V?) -> Where { return !Where(attribute.keyPath, isEqualTo: value) diff --git a/Sources/Importing/BaseDataTransaction+Importing.swift b/Sources/Importing/BaseDataTransaction+Importing.swift index d368f9c..d76abc4 100644 --- a/Sources/Importing/BaseDataTransaction+Importing.swift +++ b/Sources/Importing/BaseDataTransaction+Importing.swift @@ -39,9 +39,9 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableObject` methods - returns: the created `ImportableObject` instance, or `nil` if the import was ignored */ - public func importObject( + public func importObject( _ into: Into, - source: T.ImportSource) throws -> T? where T: DynamicObject, T: ImportableObject { + source: T.ImportSource) throws -> T? { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -69,9 +69,9 @@ public extension BaseDataTransaction { - parameter source: the object to import values from - throws: an `Error` thrown from any of the `ImportableObject` methods */ - public func importObject( + public func importObject( _ object: T, - source: T.ImportSource) throws where T: DynamicObject, T: ImportableObject { + source: T.ImportSource) throws { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -97,9 +97,9 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableObject` methods - returns: the array of created `ImportableObject` instances */ - public func importObjects( + public func importObjects( _ into: Into, - sourceArray: S) throws -> [T] where T: DynamicObject, T: ImportableObject, S.Iterator.Element == T.ImportSource { + sourceArray: S) throws -> [T] where S.Iterator.Element == T.ImportSource { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -133,9 +133,9 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableUniqueObject` methods - returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored */ - public func importUniqueObject( + public func importUniqueObject( _ into: Into, - source: T.ImportSource) throws -> T? where T: DynamicObject, T: ImportableUniqueObject { + source: T.ImportSource) throws -> T? { CoreStore.assert( self.isRunningInAllowedQueue(), @@ -185,10 +185,10 @@ public extension BaseDataTransaction { - throws: an `Error` thrown from any of the `ImportableUniqueObject` methods - returns: the array of created/updated `ImportableUniqueObject` instances */ - public func importUniqueObjects( + public func importUniqueObjects( _ into: Into, sourceArray: S, - preProcess: @escaping (_ mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] where T: DynamicObject, T: ImportableUniqueObject, S.Iterator.Element == T.ImportSource { + preProcess: @escaping (_ mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] where S.Iterator.Element == T.ImportSource { CoreStore.assert( self.isRunningInAllowedQueue(), diff --git a/Sources/Importing/ImportableUniqueObject.swift b/Sources/Importing/ImportableUniqueObject.swift index 97f59fa..97e77d1 100644 --- a/Sources/Importing/ImportableUniqueObject.swift +++ b/Sources/Importing/ImportableUniqueObject.swift @@ -190,7 +190,7 @@ public extension ImportableUniqueObject where Self: DynamicObject { .setValue( newValue, forKvcKey: type(of: self).uniqueIDKeyPath, - willSetValue: { $0.cs_toImportableNativeType() } + willSetValue: { ($0.cs_toImportableNativeType() as! CoreDataNativeType) } ) } } diff --git a/Sources/Internal/EntityIdentifier.swift b/Sources/Internal/EntityIdentifier.swift index a12b0b0..6004139 100644 --- a/Sources/Internal/EntityIdentifier.swift +++ b/Sources/Internal/EntityIdentifier.swift @@ -48,7 +48,7 @@ internal struct EntityIdentifier: Hashable { internal init(_ type: NSManagedObject.Type) { self.category = .coreData - self.interfacedClassName = String(reflecting: type) + self.interfacedClassName = NSStringFromClass(type) } internal init(_ type: CoreStoreObject.Type) { diff --git a/Sources/Internal/FetchedResultsControllerDelegate.swift b/Sources/Internal/FetchedResultsControllerDelegate.swift index 395fdfb..dc1d251 100644 --- a/Sources/Internal/FetchedResultsControllerDelegate.swift +++ b/Sources/Internal/FetchedResultsControllerDelegate.swift @@ -54,6 +54,9 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult @nonobjc internal var enabled = true + @nonobjc + internal let taskGroup = DispatchGroup() + @nonobjc internal weak var handler: FetchedResultsControllerHandler? @@ -78,6 +81,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult @objc dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController) { + self.taskGroup.enter() guard self.enabled else { return @@ -92,6 +96,10 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult @objc dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + defer { + + self.taskGroup.leave() + } guard self.enabled else { return diff --git a/Sources/Internal/NSEntityDescription+DynamicModel.swift b/Sources/Internal/NSEntityDescription+DynamicModel.swift index b5d91d8..bfce9af 100644 --- a/Sources/Internal/NSEntityDescription+DynamicModel.swift +++ b/Sources/Internal/NSEntityDescription+DynamicModel.swift @@ -38,23 +38,29 @@ internal extension NSEntityDescription { guard let userInfo = self.userInfo, let typeName = userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] as! String?, - let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String? else { + let entityName = userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] as! String?, + let isAbstract = userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] as! Bool? else { return nil } return CoreStoreSchema.AnyEntity( type: NSClassFromString(typeName) as! CoreStoreObject.Type, - entityName: entityName + entityName: entityName, + isAbstract: isAbstract, + versionHashModifier: userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] as! String? ) } set { if let newValue = newValue { - self.userInfo = [ + var userInfo: [AnyHashable : Any] = [ UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type), - UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName + UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName, + UserInfoKey.CoreStoreManagedObjectIsAbstract: newValue.isAbstract ] + userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier + self.userInfo = userInfo } else { @@ -72,5 +78,7 @@ internal extension NSEntityDescription { fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName" fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName" + fileprivate static let CoreStoreManagedObjectIsAbstract = "CoreStoreManagedObjectIsAbstract" + fileprivate static let CoreStoreManagedObjectVersionHashModifier = "CoreStoreManagedObjectVersionHashModifier" } } diff --git a/Sources/Internal/NSManagedObject+DynamicModel.swift b/Sources/Internal/NSManagedObject+DynamicModel.swift new file mode 100644 index 0000000..c385b50 --- /dev/null +++ b/Sources/Internal/NSManagedObject+DynamicModel.swift @@ -0,0 +1,61 @@ +// +// NSManagedObject+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 Foundation +import CoreData + + +// MARK: - NSManagedObject + +internal extension NSManagedObject { + + @nonobjc + internal weak var coreStoreObject: CoreStoreObject? { + + get { + + return cs_getAssociatedObjectForKey( + &PropertyKeys.coreStoreObject, + inObject: self + ) + } + set { + + cs_setAssociatedWeakObject( + newValue, + forKey: &PropertyKeys.coreStoreObject, + inObject: self + ) + } + } + + + // MARK: Private + + private struct PropertyKeys { + + static var coreStoreObject: Void? + } +} diff --git a/Sources/Internal/NSManagedObject+Logging.swift b/Sources/Internal/NSManagedObject+Logging.swift index 91dc4ba..2db850f 100644 --- a/Sources/Internal/NSManagedObject+Logging.swift +++ b/Sources/Internal/NSManagedObject+Logging.swift @@ -48,6 +48,25 @@ internal extension NSManagedObject { } return nil } + + @nonobjc + internal func isEditableInContext() -> Bool? { + + guard let context = self.managedObjectContext else { + + return nil + } + if context.isTransactionContext { + + return true + } + if context.isDataStackContext { + + return false + } + return nil + } + // TODO: test before release (rolled back) // @nonobjc // internal static func cs_swizzleMethodsForLogging() { diff --git a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift index ecce091..0fbb2c5 100644 --- a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift @@ -400,9 +400,9 @@ extension LegacySQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringC } -// MARK: - LegacyXcodeDataModel +// MARK: - LegacyXcodeDataModelSchema -extension LegacyXcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { +extension LegacyXcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { // MARK: CustomDebugStringConvertible @@ -1045,9 +1045,9 @@ extension VersionLock: CustomStringConvertible, CustomDebugStringConvertible, Co } -// MARK: - XcodeDataModel +// MARK: - XcodeDataModelSchema -extension XcodeDataModel: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { +extension XcodeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { // MARK: CustomDebugStringConvertible diff --git a/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift new file mode 100644 index 0000000..fd6b271 --- /dev/null +++ b/Sources/Migrating/Migration Mapping Providers/MigrationMappingProvider.swift @@ -0,0 +1,133 @@ +// +// MigrationMappingProvider.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: - SchemaMappingProvider + +public protocol SchemaMappingProvider { + + var sourceSchema: DynamicSchema { get } + var destinationSchema: DynamicSchema { get } + + func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) +} + +public protocol EntityMappingProvider { + + var source: (schema: DynamicSchema, entity: DynamicEntity) { get } + var destination: (schema: DynamicSchema, entity: DynamicEntity) { get } + + func createEntityMapping() -> NSEntityMapping +} + + +// MARK: - XcodeMappingModelProvider + +open class XcodeMappingModelProvider: SchemaMappingProvider { + + private let mappingModelBundles: [Bundle] + + public required init(source: SourceSchema, destination: DestinationSchema, mappingModelBundles: [Bundle] = Bundle.allBundles) { + + self.sourceSchema = source + self.destinationSchema = destination + self.mappingModelBundles = mappingModelBundles + } + + + // MARK: SchemaMappingProvider + + public typealias SourceSchema = S + public typealias DestinationSchema = D + + public let sourceSchema: SourceSchema + public let destinationSchema: DestinationSchema + + public func createMappingModel() throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) { + + let sourceModel = self.sourceSchema.rawModel() + let destinationModel = self.destinationSchema.rawModel() + + if let mappingModel = NSMappingModel( + from: self.mappingModelBundles, + forSourceModel: sourceModel, + destinationModel: destinationModel) { + + return ( + mappingModel, + .heavyweight( + sourceVersion: self.sourceSchema.modelVersion, + destinationVersion: self.destinationSchema.modelVersion + ) + ) + } + + let mappingModel = try NSMappingModel.inferredMappingModel( + forSourceModel: sourceModel, + destinationModel: destinationModel + ) + return ( + mappingModel, + .lightweight( + sourceVersion: self.sourceSchema.modelVersion, + destinationVersion: self.destinationSchema.modelVersion + ) + ) + } +} + + +// MARK: - UnsafeMigrationProxyObject + +public final class UnsafeMigrationProxyObject { + + public subscript(kvcKey: KeyPath) -> Any? { + + get { + + return self.rawObject.cs_accessValueForKVCKey(kvcKey) + } + set { + + self.rawObject.cs_setValue(newValue, forKVCKey: kvcKey) + } + } + + + // MARK: Internal + + internal init(_ rawObject: NSManagedObject) { + + self.rawObject = rawObject + } + + + // MARK: Private + + private let rawObject: NSManagedObject +} diff --git a/Sources/ObjectiveC/CSListMonitor.swift b/Sources/ObjectiveC/CSListMonitor.swift index 071210f..6e109d6 100644 --- a/Sources/ObjectiveC/CSListMonitor.swift +++ b/Sources/ObjectiveC/CSListMonitor.swift @@ -546,7 +546,7 @@ public final class CSListMonitor: NSObject { // MARK: - ListMonitor @available(OSX 10.12, *) -extension ListMonitor where T: NSManagedObject { +extension ListMonitor where ListMonitor.ObjectType: NSManagedObject { // MARK: CoreStoreSwiftType diff --git a/Sources/ObjectiveC/CSObjectMonitor.swift b/Sources/ObjectiveC/CSObjectMonitor.swift index aca5a49..52e58dd 100644 --- a/Sources/ObjectiveC/CSObjectMonitor.swift +++ b/Sources/ObjectiveC/CSObjectMonitor.swift @@ -138,7 +138,7 @@ public final class CSObjectMonitor: NSObject { // MARK: - ObjectMonitor @available(OSX 10.12, *) -extension ObjectMonitor where EntityType: NSManagedObject { +extension ObjectMonitor where ObjectMonitor.ObjectType: NSManagedObject { // MARK: CoreStoreSwiftType diff --git a/Sources/Observing/CoreStore+Observing.swift b/Sources/Observing/CoreStore+Observing.swift index 86c5a14..0bc78be 100644 --- a/Sources/Observing/CoreStore+Observing.swift +++ b/Sources/Observing/CoreStore+Observing.swift @@ -38,7 +38,7 @@ public extension CoreStore { - parameter object: the `NSManagedObject` to observe changes from - returns: a `ObjectMonitor` that monitors changes to `object` */ - public static func monitorObject(_ object: T) -> ObjectMonitor { + public static func monitorObject(_ object: T) -> ObjectMonitor { return self.defaultStack.monitorObject(object) } @@ -50,7 +50,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { + public static func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { return self.defaultStack.monitorList(from, fetchClauses) } @@ -62,7 +62,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { + public static func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { return self.defaultStack.monitorList(from, fetchClauses) } @@ -74,7 +74,7 @@ public extension CoreStore { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -86,7 +86,7 @@ public extension CoreStore { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + public static func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -99,7 +99,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { + public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -112,7 +112,7 @@ public extension CoreStore { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { + public static func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -125,7 +125,7 @@ public extension CoreStore { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } @@ -138,7 +138,7 @@ public extension CoreStore { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + public static func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } diff --git a/Sources/Observing/DataStack+Observing.swift b/Sources/Observing/DataStack+Observing.swift index c408637..1070b42 100644 --- a/Sources/Observing/DataStack+Observing.swift +++ b/Sources/Observing/DataStack+Observing.swift @@ -38,7 +38,7 @@ public extension DataStack { - parameter object: the `NSManagedObject` to observe changes from - returns: a `ObjectMonitor` that monitors changes to `object` */ - public func monitorObject(_ object: T) -> ObjectMonitor { + public func monitorObject(_ object: T) -> ObjectMonitor { CoreStore.assert( Thread.isMainThread, @@ -54,7 +54,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorList(from, fetchClauses) } @@ -66,7 +66,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( Thread.isMainThread, @@ -95,7 +95,7 @@ public extension DataStack { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -107,7 +107,7 @@ public extension DataStack { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { CoreStore.assert( Thread.isMainThread, @@ -138,7 +138,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -151,7 +151,7 @@ public extension DataStack { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( Thread.isMainThread, @@ -182,7 +182,7 @@ public extension DataStack { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } @@ -195,7 +195,7 @@ public extension DataStack { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { CoreStore.assert( Thread.isMainThread, diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index 4d5a53e..674d669 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -67,17 +67,22 @@ import CoreData In the example above, both `person1` and `person2` will contain the object at section=2, index=3. */ @available(OSX 10.12, *) -public final class ListMonitor: Hashable { +public final class ListMonitor: Hashable { // MARK: Public (Accessors) + /** + The type for the objects contained bye the `ListMonitor` + */ + public typealias ObjectType = D + /** Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`. - parameter index: the index of the object. Using an index above the valid range will raise an exception. - returns: the `NSManagedObject` at the specified index */ - public subscript(index: Int) -> T { + public subscript(index: Int) -> ObjectType { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, @@ -85,7 +90,7 @@ public final class ListMonitor: Hashable { ) if self.isSectioned { - return T.cs_fromRaw(object: self.fetchedResultsController.fetchedObjects![index]) + return ObjectType.cs_fromRaw(object: self.fetchedResultsController.fetchedObjects![index]) } return self[0, index] } @@ -96,14 +101,14 @@ public final class ListMonitor: Hashable { - parameter index: the index for the object. Using an index above the valid range will return `nil`. - returns: the `NSManagedObject` at the specified index, or `nil` if out of bounds */ - public subscript(safeIndex index: Int) -> T? { + public subscript(safeIndex index: Int) -> ObjectType? { if self.isSectioned { let fetchedObjects = self.fetchedResultsController.fetchedObjects! if index < fetchedObjects.count && index >= 0 { - return T.cs_fromRaw(object: fetchedObjects[index]) + return ObjectType.cs_fromRaw(object: fetchedObjects[index]) } return nil } @@ -117,7 +122,7 @@ public final class ListMonitor: Hashable { - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will raise an exception. - returns: the `NSManagedObject` at the specified section and item index */ - public subscript(sectionIndex: Int, itemIndex: Int) -> T { + public subscript(sectionIndex: Int, itemIndex: Int) -> ObjectType { return self[NSIndexPath(indexes: [sectionIndex, itemIndex], length: 2) as IndexPath] } @@ -129,7 +134,7 @@ public final class ListMonitor: Hashable { - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`. - returns: the `NSManagedObject` at the specified section and item index, or `nil` if out of bounds */ - public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? { + public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectType? { guard let section = self.sectionInfoAtIndex(safeSectionIndex: sectionIndex) else { @@ -139,7 +144,7 @@ public final class ListMonitor: Hashable { return nil } - return T.cs_fromRaw(object: section.objects![itemIndex] as! NSManagedObject) + return ObjectType.cs_fromRaw(object: section.objects![itemIndex] as! NSManagedObject) } /** @@ -148,13 +153,13 @@ public final class ListMonitor: Hashable { - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will raise an exception. - returns: the `NSManagedObject` at the specified index path */ - public subscript(indexPath: IndexPath) -> T { + public subscript(indexPath: IndexPath) -> ObjectType { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) - return T.cs_fromRaw(object: self.fetchedResultsController.object(at: indexPath)) + return ObjectType.cs_fromRaw(object: self.fetchedResultsController.object(at: indexPath)) } /** @@ -163,7 +168,7 @@ public final class ListMonitor: Hashable { - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will return `nil`. - returns: the `NSManagedObject` at the specified index path, or `nil` if out of bounds */ - public subscript(safeIndexPath indexPath: IndexPath) -> T? { + public subscript(safeIndexPath indexPath: IndexPath) -> ObjectType? { return self[ safeSectionIndex: indexPath[0], @@ -340,7 +345,7 @@ public final class ListMonitor: Hashable { - parameter object: the `NSManagedObject` to search the index of - returns: the index of the `NSManagedObject` if it exists in the `ListMonitor`'s fetched objects, or `nil` if not found. */ - public func indexOf(_ object: T) -> Int? { + public func indexOf(_ object: ObjectType) -> Int? { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, @@ -359,7 +364,7 @@ public final class ListMonitor: Hashable { - parameter object: the `NSManagedObject` to search the index of - returns: the `NSIndexPath` of the `NSManagedObject` if it exists in the `ListMonitor`'s fetched objects, or `nil` if not found. */ - public func indexPathOf(_ object: T) -> IndexPath? { + public func indexPathOf(_ object: ObjectType) -> IndexPath? { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, @@ -382,7 +387,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ListEntityType == T { + public func addObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -417,7 +422,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListObjectObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ListEntityType == T { + public func addObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -471,7 +476,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListSectionObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ListEntityType == T { + public func addObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -532,7 +537,7 @@ public final class ListMonitor: Hashable { - parameter observer: a `ListObserver` to unregister notifications to */ - public func removeObserver(_ observer: U) where U.ListEntityType == T { + public func removeObserver(_ observer: U) where U.ListEntityType == ObjectType { self.unregisterObserver(observer) } @@ -550,7 +555,7 @@ public final class ListMonitor: Hashable { `refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes. - - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`. */ public func refetch(_ fetchClauses: FetchClause...) { @@ -562,7 +567,7 @@ public final class ListMonitor: Hashable { `refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes. - - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Note that only specified clauses will be changed; unspecified clauses will use previous values. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`. */ public func refetch(_ fetchClauses: [FetchClause]) { @@ -573,9 +578,24 @@ public final class ListMonitor: Hashable { } + // MARK: Public (3rd Party Utilities) + + /** + Allow external libraries to store custom data in the `ListMonitor`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + monitor.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + */ + private let userInfo = UserInfo() + + // MARK: Equatable - public static func == (lhs: ListMonitor, rhs: ListMonitor) -> Bool { + public static func == (lhs: ListMonitor, rhs: ListMonitor) -> Bool { return lhs.fetchedResultsController === rhs.fetchedResultsController } @@ -585,7 +605,7 @@ public final class ListMonitor: Hashable { return lhs.fetchedResultsController === rhs.fetchedResultsController } - public static func ~= (lhs: ListMonitor, rhs: ListMonitor) -> Bool { + public static func ~= (lhs: ListMonitor, rhs: ListMonitor) -> Bool { return lhs.fetchedResultsController === rhs.fetchedResultsController } @@ -606,7 +626,7 @@ public final class ListMonitor: Hashable { // MARK: Internal - internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { + internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { self.init( context: dataStack.mainContext, @@ -618,7 +638,7 @@ public final class ListMonitor: Hashable { ) } - internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { + internal convenience init(dataStack: DataStack, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { self.init( context: dataStack.mainContext, @@ -630,7 +650,7 @@ public final class ListMonitor: Hashable { ) } - internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { + internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) { self.init( context: unsafeTransaction.context, @@ -642,7 +662,7 @@ public final class ListMonitor: Hashable { ) } - internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { + internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: @escaping (ListMonitor) -> Void) { self.init( context: unsafeTransaction.context, @@ -654,7 +674,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor) -> Void) { + internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -674,7 +694,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ object: T, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) { + internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ object: ObjectType, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -684,7 +704,7 @@ public final class ListMonitor: Hashable { guard let `self` = self, let userInfo = note.userInfo, - let object = userInfo[String(describing: NSManagedObject.self)] as? T else { + let object = userInfo[String(describing: NSManagedObject.self)] as? ObjectType else { return } @@ -701,7 +721,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) { + internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -724,7 +744,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObserver(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void) { + internal func registerObserver(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -784,7 +804,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObserver(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: T, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) { + internal func registerObserver(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor, _ object: ObjectType, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -845,7 +865,7 @@ public final class ListMonitor: Hashable { ) } - internal func registerObserver(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) { + internal func registerObserver(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -926,8 +946,12 @@ public final class ListMonitor: Hashable { return } - self.fetchedResultsControllerDelegate.enabled = false - self.applyFetchClauses(self.fetchedResultsController.fetchRequest) + let (newFetchedResultsController, newFetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController( + context: self.fetchedResultsController.managedObjectContext, + from: self.from, + sectionBy: self.sectionBy, + applyFetchClauses: self.applyFetchClauses + ) self.transactionQueue.async { [weak self] in @@ -936,16 +960,21 @@ public final class ListMonitor: Hashable { return } - try! self.fetchedResultsController.performFetchFromSpecifiedStores() - - DispatchQueue.main.async { [weak self] () -> Void in + try! newFetchedResultsController.performFetchFromSpecifiedStores() + self.fetchedResultsControllerDelegate.taskGroup.notify(queue: .main) { + + self.fetchedResultsControllerDelegate.enabled = false + } + newFetchedResultsControllerDelegate.taskGroup.notify(queue: .main) { [weak self] () -> Void in guard let `self` = self else { return } - self.fetchedResultsControllerDelegate.enabled = true + (self.fetchedResultsController, self.fetchedResultsControllerDelegate) = (newFetchedResultsController, newFetchedResultsControllerDelegate) + newFetchedResultsControllerDelegate.handler = self + self.isPendingRefetch = false NotificationCenter.default.post( @@ -966,7 +995,7 @@ public final class ListMonitor: Hashable { // MARK: Private - fileprivate let fetchedResultsController: CoreStoreFetchedResultsController + fileprivate var fetchedResultsController: CoreStoreFetchedResultsController fileprivate let taskGroup = DispatchGroup() fileprivate let sectionIndexTransformer: (_ sectionName: KeyPath?) -> String? @@ -985,7 +1014,7 @@ public final class ListMonitor: Hashable { private var didInsertSectionKey: Void? private var didDeleteSectionKey: Void? - private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate + private var fetchedResultsControllerDelegate: FetchedResultsControllerDelegate private var observerForWillChangePersistentStore: NotificationObserver! private var observerForDidChangePersistentStore: NotificationObserver! private let transactionQueue: DispatchQueue @@ -1012,9 +1041,7 @@ public final class ListMonitor: Hashable { } } - private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: ((ListMonitor) -> Void)?) { - - self.isSectioned = (sectionBy != nil) + private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void) -> (controller: CoreStoreFetchedResultsController, delegate: FetchedResultsControllerDelegate) { let fetchRequest = CoreStoreFetchRequest() fetchRequest.fetchLimit = 0 @@ -1032,9 +1059,25 @@ public final class ListMonitor: Hashable { ) let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate() + fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - self.fetchedResultsController = fetchedResultsController - self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate + return (fetchedResultsController, fetchedResultsControllerDelegate) + } + + private let from: From + private let sectionBy: SectionBy? + + private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest) -> Void, createAsynchronously: ((ListMonitor) -> Void)?) { + + self.isSectioned = (sectionBy != nil) + self.from = from + self.sectionBy = sectionBy + (self.fetchedResultsController, self.fetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController( + context: context, + from: from, + sectionBy: sectionBy, + applyFetchClauses: applyFetchClauses + ) if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer { @@ -1046,9 +1089,7 @@ public final class ListMonitor: Hashable { } self.transactionQueue = transactionQueue self.applyFetchClauses = applyFetchClauses - - fetchedResultsControllerDelegate.handler = self - fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController + self.fetchedResultsControllerDelegate.handler = self guard let coordinator = context.parentStack?.coordinator else { @@ -1109,7 +1150,7 @@ public final class ListMonitor: Hashable { transactionQueue.async { - try! fetchedResultsController.performFetchFromSpecifiedStores() + try! self.fetchedResultsController.performFetchFromSpecifiedStores() self.taskGroup.notify(queue: .main) { createAsynchronously(self) @@ -1118,29 +1159,29 @@ public final class ListMonitor: Hashable { } else { - try! fetchedResultsController.performFetchFromSpecifiedStores() + try! self.fetchedResultsController.performFetchFromSpecifiedStores() } } } -// MARK: - ListMonitor where T: NSManagedObject +// MARK: - ListMonitor where ListMonitor.ObjectType: NSManagedObject @available(OSX 10.12, *) -extension ListMonitor where T: NSManagedObject { +extension ListMonitor where ListMonitor.ObjectType: NSManagedObject { /** Returns all objects in all sections - returns: all objects in all sections */ - public func objectsInAllSections() -> [T] { + public func objectsInAllSections() -> [ObjectType] { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) - return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController).fetchedObjects ?? [] + return (self.fetchedResultsController.dynamicCast() as NSFetchedResultsController).fetchedObjects ?? [] } /** @@ -1149,9 +1190,9 @@ extension ListMonitor where T: NSManagedObject { - parameter section: the section index. Using an index outside the valid range will raise an exception. - returns: all objects in the specified section */ - public func objectsInSection(_ section: Int) -> [T] { + public func objectsInSection(_ section: Int) -> [ObjectType] { - return (self.sectionInfoAtIndex(section).objects as! [T]?) ?? [] + return (self.sectionInfoAtIndex(section).objects as! [ObjectType]?) ?? [] } /** @@ -1160,31 +1201,31 @@ extension ListMonitor where T: NSManagedObject { - parameter section: the section index. Using an index outside the valid range will return `nil`. - returns: all objects in the specified section */ - public func objectsInSection(safeSectionIndex section: Int) -> [T]? { + public func objectsInSection(safeSectionIndex section: Int) -> [ObjectType]? { - return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [T]? + return self.sectionInfoAtIndex(safeSectionIndex: section)?.objects as! [ObjectType]? } } -// MARK: - ListMonitor where T: CoreStoreObject +// MARK: - ListMonitor where ListMonitor.ObjectType: CoreStoreObject @available(OSX 10.12, *) -extension ListMonitor where T: CoreStoreObject { +extension ListMonitor where ListMonitor.ObjectType: CoreStoreObject { /** Returns all objects in all sections - returns: all objects in all sections */ - public func objectsInAllSections() -> [T] { + public func objectsInAllSections() -> [ObjectType] { CoreStore.assert( !self.isPendingRefetch || Thread.isMainThread, "Attempted to access a \(cs_typeName(self)) outside the main thread while a refetch is in progress." ) return (self.fetchedResultsController.fetchedObjects ?? []) - .map(T.cs_fromRaw) + .map(ObjectType.cs_fromRaw) } /** @@ -1193,10 +1234,10 @@ extension ListMonitor where T: CoreStoreObject { - parameter section: the section index. Using an index outside the valid range will raise an exception. - returns: all objects in the specified section */ - public func objectsInSection(_ section: Int) -> [T] { + public func objectsInSection(_ section: Int) -> [ObjectType] { return (self.sectionInfoAtIndex(section).objects ?? []) - .map({ T.cs_fromRaw(object: $0 as! NSManagedObject) }) + .map({ ObjectType.cs_fromRaw(object: $0 as! NSManagedObject) }) } /** @@ -1205,10 +1246,10 @@ extension ListMonitor where T: CoreStoreObject { - parameter section: the section index. Using an index outside the valid range will return `nil`. - returns: all objects in the specified section */ - public func objectsInSection(safeSectionIndex section: Int) -> [T]? { + public func objectsInSection(safeSectionIndex section: Int) -> [ObjectType]? { return (self.sectionInfoAtIndex(safeSectionIndex: section)?.objects)? - .map({ T.cs_fromRaw(object: $0 as! NSManagedObject) }) + .map({ ObjectType.cs_fromRaw(object: $0 as! NSManagedObject) }) } } @@ -1306,12 +1347,15 @@ extension ListMonitor: FetchedResultsControllerHandler { } internal func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - + + defer { + + self.taskGroup.leave() + } NotificationCenter.default.post( name: Notification.Name.listMonitorDidChangeList, object: self ) - self.taskGroup.leave() } internal func controller(_ controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { diff --git a/Sources/Observing/ObjectMonitor.swift b/Sources/Observing/ObjectMonitor.swift index 8ab8047..85cbb1b 100644 --- a/Sources/Observing/ObjectMonitor.swift +++ b/Sources/Observing/ObjectMonitor.swift @@ -40,17 +40,22 @@ import CoreData Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. */ @available(OSX 10.12, *) -public final class ObjectMonitor: Equatable { +public final class ObjectMonitor: Equatable { + + /** + The type for the object contained by the `ObjectMonitor` + */ + public typealias ObjectType = D /** Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted. */ - public var object: EntityType? { + public var object: ObjectType? { return self.fetchedResultsController .fetchedObjects? .first - .flatMap({ EntityType.cs_fromRaw(object: $0) }) + .flatMap({ ObjectType.cs_fromRaw(object: $0) }) } /** @@ -72,7 +77,7 @@ public final class ObjectMonitor: Equatable { - parameter observer: an `ObjectObserver` to send change notifications to */ - public func addObserver(_ observer: U) where U.ObjectEntityType == EntityType { + public func addObserver(_ observer: U) where U.ObjectEntityType == ObjectType { self.unregisterObserver(observer) self.registerObserver( @@ -99,15 +104,30 @@ public final class ObjectMonitor: Equatable { - parameter observer: an `ObjectObserver` to unregister notifications to */ - public func removeObserver(_ observer: U) where U.ObjectEntityType == EntityType { + public func removeObserver(_ observer: U) where U.ObjectEntityType == ObjectType { self.unregisterObserver(observer) } + // MARK: Public (3rd Party Utilities) + + /** + Allow external libraries to store custom data in the `ObjectMonitor`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + monitor.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + */ + private let userInfo = UserInfo() + + // MARK: Equatable - public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + public static func == (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { return lhs === rhs } @@ -117,7 +137,7 @@ public final class ObjectMonitor: Equatable { return lhs.fetchedResultsController === rhs.fetchedResultsController } - public static func ~= (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { + public static func ~= (lhs: ObjectMonitor, rhs: ObjectMonitor) -> Bool { return lhs === rhs } @@ -138,17 +158,17 @@ public final class ObjectMonitor: Equatable { // MARK: Internal - internal convenience init(dataStack: DataStack, object: EntityType) { + internal convenience init(dataStack: DataStack, object: ObjectType) { self.init(context: dataStack.mainContext, object: object) } - internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: EntityType) { + internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: ObjectType) { self.init(context: unsafeTransaction.context, object: object) } - internal func registerObserver(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: EntityType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: EntityType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: EntityType, _ changedPersistentKeys: Set) -> Void) { + internal func registerObserver(_ observer: U, willChangeObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: ObjectType) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: ObjectType) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ObjectMonitor, _ object: ObjectType, _ changedPersistentKeys: Set) -> Void) { CoreStore.assert( Thread.isMainThread, @@ -238,7 +258,7 @@ public final class ObjectMonitor: Equatable { private var didDeleteObjectKey: Void? private var didUpdateObjectKey: Void? - private init(context: NSManagedObjectContext, object: EntityType) { + private init(context: NSManagedObjectContext, object: ObjectType) { let rawObject = object.cs_toRaw() let fetchRequest = CoreStoreFetchRequest() @@ -253,7 +273,7 @@ public final class ObjectMonitor: Equatable { let fetchedResultsController = CoreStoreFetchedResultsController( context: context, fetchRequest: fetchRequest.dynamicCast(), - from: nil as From?, + from: nil as From?, applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest ) @@ -269,7 +289,7 @@ public final class ObjectMonitor: Equatable { self.lastCommittedAttributes = (self.object?.cs_toRaw().committedValues(forKeys: nil) as? [String: NSObject]) ?? [:] } - private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor) -> Void) { + private func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -289,7 +309,7 @@ public final class ObjectMonitor: Equatable { ) } - private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor, _ object: EntityType) -> Void) { + private func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ObjectMonitor, _ object: ObjectType) -> Void) { cs_setAssociatedRetainedObject( NotificationObserver( @@ -303,7 +323,7 @@ public final class ObjectMonitor: Equatable { return } - callback(self, EntityType.cs_fromRaw(object: object)) + callback(self, ObjectType.cs_fromRaw(object: object)) } ), forKey: notificationKey, diff --git a/Sources/Observing/UnsafeDataTransaction+Observing.swift b/Sources/Observing/UnsafeDataTransaction+Observing.swift index 7e7ac14..e8ac0b7 100644 --- a/Sources/Observing/UnsafeDataTransaction+Observing.swift +++ b/Sources/Observing/UnsafeDataTransaction+Observing.swift @@ -38,7 +38,7 @@ public extension UnsafeDataTransaction { - parameter object: the `NSManagedObject` to observe changes from - returns: a `ObjectMonitor` that monitors changes to `object` */ - public func monitorObject(_ object: T) -> ObjectMonitor { + public func monitorObject(_ object: T) -> ObjectMonitor { return ObjectMonitor( unsafeTransaction: self, @@ -53,7 +53,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorList(from, fetchClauses) } @@ -65,7 +65,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorList(_ from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, @@ -90,7 +90,7 @@ public extension UnsafeDataTransaction { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: FetchClause...) { self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses) } @@ -102,7 +102,7 @@ public extension UnsafeDataTransaction { - parameter from: a `From` clause indicating the entity type - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { + public func monitorList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ fetchClauses: [FetchClause]) { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, @@ -129,7 +129,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { return self.monitorSectionedList(from, sectionBy, fetchClauses) } @@ -142,7 +142,7 @@ public extension UnsafeDataTransaction { - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - returns: a `ListMonitor` instance that monitors changes to the list */ - public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { + public func monitorSectionedList(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, @@ -168,7 +168,7 @@ public extension UnsafeDataTransaction { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) { self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses) } @@ -181,7 +181,7 @@ public extension UnsafeDataTransaction { - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. */ - public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { + public func monitorSectionedList(createAsynchronously: @escaping (ListMonitor) -> Void, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) { CoreStore.assert( fetchClauses.filter { $0 is OrderBy }.count > 0, diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index eb42b7a..7c926ed 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -96,6 +96,14 @@ public final class DataStack: Equatable { return self.schemaHistory.currentModelVersion } + /** + Returns the `DataStack`'s model schema. + */ + public var modelSchema: DynamicSchema { + + return self.schemaHistory.schemaByVersion[self.schemaHistory.currentModelVersion]! + } + /** Returns the entity name-to-class type mapping from the `DataStack`'s model. */ @@ -141,7 +149,7 @@ public final class DataStack: Equatable { let actualType = anyEntity.type if (actualType as AnyClass).isSubclass(of: type) { - entityTypesByName[entityDescription.name!] = actualType + entityTypesByName[entityDescription.name!] = (actualType as! CoreStoreObject.Type) } } } @@ -442,6 +450,21 @@ public final class DataStack: Equatable { } + // MARK: 3rd Party Utilities + + /** + Allow external libraries to store custom data in the `DataStack`. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + */ + private let userInfo = UserInfo() + + // MARK: Equatable public static func == (lhs: DataStack, rhs: DataStack) -> Bool { @@ -596,14 +619,14 @@ 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. */ - @available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModel instance as argument") + @available(*, deprecated: 3.1, message: "Use the new DataStack.init(schemaHistory:) initializer passing a LegacyXcodeDataModelSchema instance as argument") public convenience init(model: NSManagedObjectModel, migrationChain: MigrationChain = nil) { let modelVersion = migrationChain.leafVersions.first! self.init( schemaHistory: SchemaHistory( allSchema: [ - LegacyXcodeDataModel( + LegacyXcodeDataModelSchema( modelName: modelVersion, model: model ) diff --git a/Sources/Setup/Dynamic Models/CoreStoreObject.swift b/Sources/Setup/Dynamic Models/CoreStoreObject.swift index 6a2a74d..1dfcd9d 100644 --- a/Sources/Setup/Dynamic Models/CoreStoreObject.swift +++ b/Sources/Setup/Dynamic Models/CoreStoreObject.swift @@ -29,15 +29,42 @@ import Foundation // MARK: - CoreStoreObject -open class CoreStoreObject: DynamicObject, Hashable { +/** + The `CoreStoreObject` is an abstract class for creating CoreStore-managed objects that are more type-safe and more convenient than `NSManagedObject` subclasses. The model entities for `CoreStoreObject` subclasses are inferred from the subclasses' Swift declaration themselves; no .xcdatamodeld files needed. To declare persisted attributes and relationships for the `CoreStoreObject` subclass, declare properties of type `Value.Required`, `Value.Optional` for values, or `Relationship.ToOne`, `Relationship.ToManyOrdered`, `Relationship.ToManyUnordered` for relationships. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species") + let nickname = Value.Optional("nickname") + let master = Relationship.ToOne("master") + } + + class Person: CoreStoreObject { + let name = Value.Required("name") + let pet = Relationship.ToOne("pet", inverse: { $0.master }) + } + ``` + `CoreStoreObject` entities for a model version should be added to `CoreStoreSchema` instance. + - SeeAlso: CoreStoreSchema + - SeeAlso: CoreStoreObject.Value + - SeeAlso: CoreStoreObject.Relationship + */ +open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { - public required init(_ object: NSManagedObject) { + /** + Do not call this directly. This is exposed as public only as a required initializer. + - Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations. + */ + public required init(rawObject: NSManagedObject) { self.isMeta = false - self.rawObject = object - self.initializeAttributes(Mirror(reflecting: self), { [unowned object] in object }) + self.rawObject = rawObject + self.initializeAttributes(Mirror(reflecting: self), { [unowned self] in self }) } + /** + Do not call this directly. This is exposed as public only as a required initializer. + - Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations. + */ public required init(asMeta: Void) { self.isMeta = true @@ -78,18 +105,18 @@ open class CoreStoreObject: DynamicObject, Hashable { // MARK: Private - private func initializeAttributes(_ mirror: Mirror, _ accessRawObject: @escaping () -> NSManagedObject) { + private func initializeAttributes(_ mirror: Mirror, _ parentObject: @escaping () -> CoreStoreObject) { - _ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, accessRawObject) }) + _ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, parentObject) }) for child in mirror.children { switch child.value { case let property as AttributeProtocol: - property.accessRawObject = accessRawObject + property.parentObject = parentObject case let property as RelationshipProtocol: - property.accessRawObject = accessRawObject + property.parentObject = parentObject default: continue diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift index 6a8cc12..869c52e 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/CoreStoreSchema.swift @@ -31,7 +31,7 @@ import Foundation public final class CoreStoreSchema: DynamicSchema { - public convenience init(modelVersion: String, entities: [DynamicEntity], versionLock: VersionLock? = nil) { + public convenience init(modelVersion: ModelVersion, entities: [DynamicEntity], versionLock: VersionLock? = nil) { self.init( modelVersion: modelVersion, @@ -40,7 +40,7 @@ public final class CoreStoreSchema: DynamicSchema { ) } - public required init(modelVersion: String, entitiesByConfiguration: [String: [DynamicEntity]], versionLock: VersionLock? = nil) { + public required init(modelVersion: ModelVersion, entitiesByConfiguration: [String: [DynamicEntity]], versionLock: VersionLock? = nil) { var actualEntitiesByConfiguration: [String: Set] = [:] for (configuration, entities) in entitiesByConfiguration { @@ -129,14 +129,20 @@ public final class CoreStoreSchema: DynamicSchema { internal init(_ entity: DynamicEntity) { - self.type = entity.type - self.entityName = entity.entityName + self.init( + type: entity.type, + entityName: entity.entityName, + isAbstract: entity.isAbstract, + versionHashModifier: entity.versionHashModifier + ) } - internal init(type: CoreStoreObject.Type, entityName: String) { + internal init(type: DynamicObject.Type, entityName: String, isAbstract: Bool, versionHashModifier: String?) { self.type = type self.entityName = entityName + self.isAbstract = isAbstract + self.versionHashModifier = versionHashModifier } @@ -146,6 +152,8 @@ public final class CoreStoreSchema: DynamicSchema { return lhs.type == rhs.type && lhs.entityName == rhs.entityName + && lhs.isAbstract == rhs.isAbstract + && lhs.versionHashModifier == rhs.versionHashModifier } // MARK: Hashable @@ -154,12 +162,16 @@ public final class CoreStoreSchema: DynamicSchema { return ObjectIdentifier(self.type).hashValue ^ self.entityName.hashValue + ^ self.isAbstract.hashValue + ^ (self.versionHashModifier ?? "").hashValue } // MARK: DynamicEntity - internal let type: CoreStoreObject.Type + internal let type: DynamicObject.Type internal let entityName: EntityName + internal let isAbstract: Bool + internal let versionHashModifier: String? } @@ -193,6 +205,7 @@ public final class CoreStoreSchema: DynamicSchema { let entityDescription = NSEntityDescription() entityDescription.anyEntity = entity entityDescription.name = entity.entityName + entityDescription.isAbstract = entity.isAbstract entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self) func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { @@ -210,17 +223,19 @@ public final class CoreStoreSchema: DynamicSchema { description.isIndexed = attribute.isIndexed description.defaultValue = attribute.defaultValue description.isTransient = attribute.isTransient - // TODO: versionHash, renamingIdentifier, etc + description.versionHashModifier = attribute.versionHashModifier + description.renamingIdentifier = attribute.renamingIdentifier propertyDescriptions.append(description) case let relationship as RelationshipProtocol: let description = NSRelationshipDescription() description.name = relationship.keyPath - description.minCount = 0 - description.maxCount = relationship.isToMany ? 0 : 1 + description.minCount = relationship.minCount + description.maxCount = relationship.maxCount description.isOrdered = relationship.isOrdered description.deleteRule = relationship.deleteRule - // TODO: versionHash, renamingIdentifier, etc + description.versionHashModifier = relationship.versionHashModifier + description.renamingIdentifier = relationship.renamingIdentifier propertyDescriptions.append(description) default: @@ -230,7 +245,7 @@ public final class CoreStoreSchema: DynamicSchema { return propertyDescriptions } - entityDescription.properties = createProperties(for: entity.type) + entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type) return entityDescription } @@ -280,7 +295,7 @@ public final class CoreStoreSchema: DynamicSchema { for (entity, entityDescription) in entityDescriptionsByEntity { let relationshipsByName = relationshipsByNameByEntity[entity]! - for child in Mirror(reflecting: entity.type.meta).children { + for child in Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta).children { switch child.value { @@ -347,7 +362,7 @@ public final class CoreStoreSchema: DynamicSchema { for (entity, entityDescription) in entityDescriptionsByEntity { connectBaseEntity( - mirror: Mirror(reflecting: entity.type.meta), + mirror: Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta), entityDescription: entityDescription ) } diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift index f18123a..09a7bf5 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/DynamicSchema.swift @@ -29,9 +29,21 @@ import Foundation // MARK: - DynamicSchema +/** + `DynamicSchema` are types that provide `NSManagedObjectModel` instances for a particular model version. CoreStore currently supports concrete types: + - `XcodeDataModelSchema`: describes models loaded from a .xcdatamodeld file. + - `LegacyXcodeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore. + - `CoreStoreSchema`: describes models written in `CoreStoreObject` Swift class declarations. + */ public protocol DynamicSchema { + /** + The version string for this model schema. + */ var modelVersion: ModelVersion { get } + /** + Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model. + */ func rawModel() -> NSManagedObjectModel } diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModelSchema.swift similarity index 92% rename from Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift rename to Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModelSchema.swift index 741d5f8..d9db734 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModel.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/LegacyXcodeDataModelSchema.swift @@ -1,5 +1,5 @@ // -// LegacyXcodeDataModel.swift +// LegacyXcodeDataModelSchema.swift // CoreStore // // Copyright © 2017 John Rommel Estropia @@ -27,9 +27,9 @@ import CoreData import Foundation -// MARK: - LegacyXcodeDataModel +// MARK: - LegacyXcodeDataModelSchema -public final class LegacyXcodeDataModel: DynamicSchema { +public final class LegacyXcodeDataModelSchema: DynamicSchema { public required init(modelName: ModelVersion, model: NSManagedObjectModel) { diff --git a/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift b/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModelSchema.swift similarity index 95% rename from Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift rename to Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModelSchema.swift index 94f1cc2..8e25ebc 100644 --- a/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModel.swift +++ b/Sources/Setup/Dynamic Models/Dynamic Schema/XcodeDataModelSchema.swift @@ -1,5 +1,5 @@ // -// XcodeDataModel.swift +// XcodeDataModelSchema.swift // CoreStore // // Copyright © 2017 John Rommel Estropia @@ -27,9 +27,9 @@ import CoreData import Foundation -// MARK: - XcodeDataModel +// MARK: - XcodeDataModelSchema -public final class XcodeDataModel: DynamicSchema { +public final class XcodeDataModelSchema: DynamicSchema { public required init(modelVersion: ModelVersion, modelVersionFileURL: URL) { diff --git a/Sources/Setup/Dynamic Models/DynamicObject.swift b/Sources/Setup/Dynamic Models/DynamicObject.swift index 2a09c09..4544073 100644 --- a/Sources/Setup/Dynamic Models/DynamicObject.swift +++ b/Sources/Setup/Dynamic Models/DynamicObject.swift @@ -37,35 +37,6 @@ public protocol DynamicObject: class { func cs_toRaw() -> NSManagedObject } -public extension DynamicObject where Self: CoreStoreObject { - - @inline(__always) - public static func keyPath(_ attribute: (Self) -> ValueContainer.Required) -> String { - - return attribute(self.meta).keyPath - } - - @inline(__always) - public static func keyPath(_ attribute: (Self) -> ValueContainer.Optional) -> String { - - return attribute(self.meta).keyPath - } - - @inline(__always) - public static func `where`(_ condition: (Self) -> Where) -> Where { - - return condition(self.meta) - } - - - // MARK: Internal - - internal static var meta: Self { - - return self.init(asMeta: ()) - } -} - // MARK: - NSManagedObject @@ -114,12 +85,23 @@ extension CoreStoreObject { context.assign(object, to: store) } - return self.init(object) + return self.cs_fromRaw(object: object) } public class func cs_fromRaw(object: NSManagedObject) -> Self { - return self.init(object) + if let coreStoreObject = object.coreStoreObject { + + @inline(__always) + func forceCast(_ value: CoreStoreObject) -> T { + + return value as! T + } + return forceCast(coreStoreObject) + } + let coreStoreObject = self.init(rawObject: object) + object.coreStoreObject = coreStoreObject + return coreStoreObject } public func cs_toRaw() -> NSManagedObject { @@ -127,3 +109,14 @@ extension CoreStoreObject { return self.rawObject! } } + + +// MARK: - Internal + +internal extension DynamicObject where Self: CoreStoreObject { + + internal static var meta: Self { + + return self.init(asMeta: ()) + } +} diff --git a/Sources/Setup/Dynamic Models/Entity.swift b/Sources/Setup/Dynamic Models/Entity.swift index 91508fd..03fe560 100644 --- a/Sources/Setup/Dynamic Models/Entity.swift +++ b/Sources/Setup/Dynamic Models/Entity.swift @@ -32,31 +32,37 @@ import ObjectiveC public protocol DynamicEntity { - var type: CoreStoreObject.Type { get } + var type: DynamicObject.Type { get } var entityName: EntityName { get } + var isAbstract: Bool { get } + var versionHashModifier: String? { get } } // MARK: Entity -public struct Entity: DynamicEntity, Hashable { +public struct Entity: DynamicEntity, Hashable { - public init(_ entityName: String) { + public init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) { - self.init(O.self, entityName) + self.init(O.self, entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier) } - public init(_ type: O.Type, _ entityName: String) { + public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) { self.type = type self.entityName = entityName + self.isAbstract = isAbstract + self.versionHashModifier = versionHashModifier } // MARK: DynamicEntity - public let type: CoreStoreObject.Type + public let type: DynamicObject.Type public let entityName: EntityName + public let isAbstract: Bool + public let versionHashModifier: String? // MARK: Equatable @@ -65,6 +71,8 @@ public struct Entity: DynamicEntity, Hashable { return lhs.type == rhs.type && lhs.entityName == rhs.entityName + && lhs.isAbstract == rhs.isAbstract + && lhs.versionHashModifier == rhs.versionHashModifier } // MARK: Hashable @@ -72,5 +80,8 @@ public struct Entity: DynamicEntity, Hashable { public var hashValue: Int { return ObjectIdentifier(self.type).hashValue + ^ self.entityName.hashValue + ^ self.isAbstract.hashValue + ^ (self.versionHashModifier ?? "").hashValue } } diff --git a/Sources/Setup/Dynamic Models/Relationship.swift b/Sources/Setup/Dynamic Models/Relationship.swift index 7cba56f..5071194 100644 --- a/Sources/Setup/Dynamic Models/Relationship.swift +++ b/Sources/Setup/Dynamic Models/Relationship.swift @@ -60,52 +60,56 @@ public enum RelationshipContainer { relationship.value = relationship2.value } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } public var value: D? { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { $0.flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) } - ) + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0.flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0?.rawObject } - ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0?.rawObject } + ) } } @@ -117,9 +121,13 @@ public enum RelationshipContainer { internal let isToMany = false internal let isOrdered = false internal let deleteRule: NSDeleteRule + internal let minCount: Int = 0 + internal let maxCount: Int = 1 internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? - internal var accessRawObject: () -> NSManagedObject = { + internal var parentObject: () -> CoreStoreObject = { CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") } @@ -127,11 +135,13 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier } } @@ -157,24 +167,24 @@ public enum RelationshipContainer { relationship.value = relationship2.value } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } // TODO: add subscripts, indexed operations for more performant single updates @@ -183,35 +193,39 @@ public enum RelationshipContainer { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { + + guard let orderedSet = $0 as! NSOrderedSet? else { - guard let orderedSet = $0 as! NSOrderedSet? else { - - return [] - } - return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) + return [] } - ) + return orderedSet.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) } - ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { NSOrderedSet(array: $0.map({ $0.rawObject! })) } + ) } } @@ -224,9 +238,13 @@ public enum RelationshipContainer { internal let isOptional = true internal let isOrdered = true internal let deleteRule: NSDeleteRule + internal let minCount: Int + internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? - internal var accessRawObject: () -> NSManagedObject = { + internal var parentObject: () -> CoreStoreObject = { CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") } @@ -234,11 +252,17 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule) { + private init(keyPath: String, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier + + let range = (max(0, minCount) ... maxCount) + self.minCount = range.lowerBound + self.maxCount = range.upperBound } } @@ -269,24 +293,24 @@ public enum RelationshipContainer { relationship.value = Set(relationship2.value) } - public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToOne, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } - public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify) { + public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) { - self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule) + self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier) } // TODO: add subscripts, indexed operations for more performant single updates @@ -295,35 +319,39 @@ public enum RelationshipContainer { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { + + guard let set = $0 as! NSSet? else { - guard let set = $0 as! NSSet? else { - - return [] - } - return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) + return [] } - ) + return Set(set.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) } - ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { NSSet(array: $0.map({ $0.rawObject! })) } + ) } } @@ -336,9 +364,13 @@ public enum RelationshipContainer { internal let isOptional = true internal let isOrdered = true internal let deleteRule: NSDeleteRule + internal let minCount: Int + internal let maxCount: Int internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) + internal let versionHashModifier: String? + internal let renamingIdentifier: String? - internal var accessRawObject: () -> NSManagedObject = { + internal var parentObject: () -> CoreStoreObject = { CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") } @@ -346,11 +378,17 @@ public enum RelationshipContainer { // MARK: Private - private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule) { + private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) { self.keyPath = keyPath self.deleteRule = deleteRule.nativeValue self.inverse = (D.self, inverseKeyPath) + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier + + let range = (max(0, minCount) ... maxCount) + self.minCount = range.lowerBound + self.maxCount = range.upperBound } } @@ -385,5 +423,9 @@ internal protocol RelationshipProtocol: class { var isOrdered: Bool { get } var deleteRule: NSDeleteRule { get } var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get } - var accessRawObject: () -> NSManagedObject { get set } + var parentObject: () -> CoreStoreObject { get set } + var versionHashModifier: String? { get } + var renamingIdentifier: String? { get } + var minCount: Int { get } + var maxCount: Int { get } } diff --git a/Sources/Setup/Dynamic Models/SchemaHistory.swift b/Sources/Setup/Dynamic Models/SchemaHistory.swift index c6dc33f..2921a5f 100644 --- a/Sources/Setup/Dynamic Models/SchemaHistory.swift +++ b/Sources/Setup/Dynamic Models/SchemaHistory.swift @@ -29,14 +29,30 @@ import Foundation // MARK: - SchemaHistory +/** + The `SchemaHistory` encapsulates all model versions that is relevant to the data model, including past versions. + - SeeAlso: SchemaHistory.currentModelVersion + - SeeAlso: SchemaHistory.migrationChain + */ public final class SchemaHistory: ExpressibleByArrayLiteral { - - // MARK: - - + /** + The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`. + */ public let currentModelVersion: ModelVersion + + /** + The version string for the current model version. The `DataStack` will try to migrate all `StorageInterface`s added to itself to this version, following the version steps provided by the `migrationChain`. + */ public let migrationChain: MigrationChain + /** + Initializes a `SchemaHistory` with all models declared in the specified (.xcdatamodeld) model file. + - Important: Use this initializer only if all model versions are either `XcodeDataModelSchema`s or `LegacyXcodeDataModelSchema`s. Do not use this initializer if even one of the model versions is a `CoreStoreSchema`; use the `SchemaHistory.init(allSchema:migrationChain:exactCurrentModelVersion:)` initializer instead. + - parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name (CFBundleName) will be used if it exists, or "CoreData" if it the bundle name was not set. + - 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: XcodeDataModelFileName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) { guard let modelFilePath = bundle.path(forResource: modelName, ofType: "momd") else { @@ -92,7 +108,7 @@ public final class SchemaHistory: ExpressibleByArrayLiteral { for modelVersion in modelVersions { let fileURL = modelFileURL.appendingPathComponent("\(modelVersion).mom", isDirectory: false) - allSchema.append(XcodeDataModel(modelVersion: modelVersion, modelVersionFileURL: fileURL)) + allSchema.append(XcodeDataModelSchema(modelVersion: modelVersion, modelVersionFileURL: fileURL)) } self.init( allSchema: allSchema, diff --git a/Sources/Setup/Dynamic Models/Value.swift b/Sources/Setup/Dynamic Models/Value.swift index 6c9b9d4..d8cd2da 100644 --- a/Sources/Setup/Dynamic Models/Value.swift +++ b/Sources/Setup/Dynamic Models/Value.swift @@ -37,6 +37,7 @@ infix operator .= : AssignmentPrecedence public extension DynamicObject where Self: CoreStoreObject { public typealias Value = ValueContainer + public typealias Transformable = TransformableContainer } @@ -58,40 +59,61 @@ public enum ValueContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false) { + public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { self.keyPath = keyPath self.isIndexed = isIndexed self.isTransient = isTransient self.defaultValue = `default`.cs_toImportableNativeType() + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier + self.customGetter = customGetter + self.customSetter = customSetter } public var value: V { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } - ) + return self.customGetter( + object, + { () -> V in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! } + ) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0.cs_toImportableNativeType() } - ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + self.customSetter( + object, + { (newValue: V) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0.cs_toImportableNativeType() } + ) + }, + newValue + ) } } @@ -109,11 +131,19 @@ public enum ValueContainer { internal let isIndexed: Bool internal let isTransient: Bool internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? - internal var accessRawObject: () -> NSManagedObject = { + internal var parentObject: () -> CoreStoreObject = { CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V + private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void } @@ -136,39 +166,60 @@ public enum ValueContainer { attribute.value = attribute2.value } - public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false) { + public init(_ keyPath: KeyPath, `default`: V? = nil, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { self.keyPath = keyPath self.isTransient = isTransient self.defaultValue = `default`?.cs_toImportableNativeType() + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier + self.customGetter = customGetter + self.customSetter = customSetter } public var value: V? { get { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - return self.accessRawObject() - .getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } - ) + return self.customGetter( + object, + { () -> V? in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) } + ) + } + ) } set { + let object = self.parentObject() as! O CoreStore.assert( - self.accessRawObject().isRunningInAllowedQueue() == true, + object.rawObject!.isRunningInAllowedQueue() == true, "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." ) - self.accessRawObject() - .setValue( - newValue, - forKvcKey: self.keyPath, - willSetValue: { $0?.cs_toImportableNativeType() } - ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + self.customSetter( + object, + { (newValue: V?) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath, + willSetValue: { $0?.cs_toImportableNativeType() } + ) + }, + newValue + ) } } @@ -185,11 +236,231 @@ public enum ValueContainer { internal let isIndexed = false internal let isTransient: Bool internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? - internal var accessRawObject: () -> NSManagedObject = { + internal var parentObject: () -> CoreStoreObject = { CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V? + private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void + } +} + + +// MARK: - TransformableContainer + +public enum TransformableContainer { + + // MARK: - Required + + public final class Required: AttributeProtocol { + + public static func .= (_ attribute: TransformableContainer.Required, _ value: V) { + + attribute.value = value + } + + public static func .= (_ attribute: TransformableContainer.Required, _ attribute2: TransformableContainer.Required) { + + attribute.value = attribute2.value + } + + public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void = { $1($2) }) { + + self.keyPath = keyPath + self.defaultValue = `default` + self.isIndexed = isIndexed + self.isTransient = isTransient + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier + self.customGetter = customGetter + self.customSetter = customSetter + } + + public var value: V { + + get { + + let object = self.parentObject() as! O + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + return self.customGetter( + object, + { () -> V in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0 as! V } + ) + } + ) + } + set { + + let object = self.parentObject() as! O + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + self.customSetter( + object, + { (newValue: V) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + }, + newValue + ) + } + } + + + // MARK: AttributeProtocol + + internal static var attributeType: NSAttributeType { + + return .transformableAttributeType + } + + public let keyPath: KeyPath + + internal let isOptional = false + internal let isIndexed: Bool + internal let isTransient: Bool + internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? + + internal var parentObject: () -> CoreStoreObject = { + + CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V + private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void + } + + + // MARK: - Optional + + public final class Optional: AttributeProtocol { + + public static func .= (_ attribute: TransformableContainer.Optional, _ value: V) { + + attribute.value = value + } + + public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Optional) { + + attribute.value = attribute2.value + } + + public static func .= (_ attribute: TransformableContainer.Optional, _ attribute2: TransformableContainer.Required) { + + attribute.value = attribute2.value + } + + public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void = { $1($2) }) { + + self.keyPath = keyPath + self.defaultValue = `default` + self.isIndexed = isIndexed + self.isTransient = isTransient + self.versionHashModifier = versionHashModifier + self.renamingIdentifier = renamingIdentifier + self.customGetter = customGetter + self.customSetter = customSetter + } + + public var value: V? { + + get { + + let object = self.parentObject() as! O + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + return self.customGetter( + object, + { () -> V? in + + return object.rawObject!.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0 as! V? } + ) + } + ) + } + set { + + let object = self.parentObject() as! O + CoreStore.assert( + object.rawObject!.isRunningInAllowedQueue() == true, + "Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue." + ) + CoreStore.assert( + object.rawObject!.isEditableInContext() == true, + "Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction." + ) + self.customSetter( + object, + { (newValue: V?) -> Void in + + object.rawObject!.setValue( + newValue, + forKvcKey: self.keyPath + ) + }, + newValue + ) + } + } + + + // MARK: AttributeProtocol + + internal static var attributeType: NSAttributeType { + + return .transformableAttributeType + } + + public let keyPath: KeyPath + + internal let isOptional = false + internal let isIndexed: Bool + internal let isTransient: Bool + internal let defaultValue: Any? + internal let versionHashModifier: String? + internal let renamingIdentifier: String? + + internal var parentObject: () -> CoreStoreObject = { + + CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.") + } + + + // MARK: Private + + private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V? + private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void } } @@ -205,5 +476,7 @@ internal protocol AttributeProtocol: class { var isIndexed: Bool { get } var isTransient: Bool { get } var defaultValue: Any? { get } - var accessRawObject: () -> NSManagedObject { get set } + var versionHashModifier: String? { get } + var renamingIdentifier: String? { get } + var parentObject: () -> CoreStoreObject { get set } } diff --git a/Sources/Transactions/AsynchronousDataTransaction.swift b/Sources/Transactions/AsynchronousDataTransaction.swift index d8a21c7..a5d2961 100644 --- a/Sources/Transactions/AsynchronousDataTransaction.swift +++ b/Sources/Transactions/AsynchronousDataTransaction.swift @@ -104,7 +104,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { - parameter object: the `NSManagedObject` type to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ object: T?) -> T? { + public override func edit(_ object: T?) -> T? { CoreStore.assert( !self.isCommitted, @@ -121,7 +121,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { - parameter objectID: the `NSManagedObjectID` for the object to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { + public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { CoreStore.assert( !self.isCommitted, diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index baefdf6..4a1a45b 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -438,6 +438,21 @@ public /*abstract*/ class BaseDataTransaction { } + // MARK: 3rd Party Utilities + + /** + Allow external libraries to store custom data in the transaction. App code should rarely have a need for this. + ``` + enum Static { + static var myDataKey: Void? + } + transaction.userInfo[&Static.myDataKey] = myObject + ``` + - Important: Do not use this method to store thread-sensitive data. + */ + private let userInfo = UserInfo() + + // MARK: Internal internal let context: NSManagedObjectContext diff --git a/Sources/Transactions/SynchronousDataTransaction.swift b/Sources/Transactions/SynchronousDataTransaction.swift index 81d4bce..75bc63f 100644 --- a/Sources/Transactions/SynchronousDataTransaction.swift +++ b/Sources/Transactions/SynchronousDataTransaction.swift @@ -71,7 +71,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction { - parameter object: the `NSManagedObject` type to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ object: T?) -> T? { + public override func edit(_ object: T?) -> T? { CoreStore.assert( !self.isCommitted, @@ -88,7 +88,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction { - parameter objectID: the `NSManagedObjectID` for the object to be edited - returns: an editable proxy for the specified `NSManagedObject`. */ - public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { + public override func edit(_ into: Into, _ objectID: NSManagedObjectID) -> T? { CoreStore.assert( !self.isCommitted,