From 5e37ee45669135b2a9fddd3de2efd2df79e5a77f Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 10 Jan 2020 17:04:51 +0900 Subject: [PATCH] Reorganize properties source files --- CoreStore.xcodeproj/project.pbxproj | 70 ++ Sources/Relationship.ToManyOrdered.swift | 459 ++++++++ Sources/Relationship.ToManyUnordered.swift | 461 ++++++++ Sources/Relationship.ToOne.swift | 387 +++++++ Sources/Relationship.swift | 1203 +------------------- Sources/Transformable.Optional.swift | 303 +++++ Sources/Transformable.Required.swift | 291 +++++ Sources/Transformable.swift | 526 +-------- Sources/Value.Optional.swift | 364 ++++++ Sources/Value.Required.swift | 347 ++++++ Sources/Value.swift | 643 +---------- 11 files changed, 2690 insertions(+), 2364 deletions(-) create mode 100644 Sources/Relationship.ToManyOrdered.swift create mode 100644 Sources/Relationship.ToManyUnordered.swift create mode 100644 Sources/Relationship.ToOne.swift create mode 100644 Sources/Transformable.Optional.swift create mode 100644 Sources/Transformable.Required.swift create mode 100644 Sources/Value.Optional.swift create mode 100644 Sources/Value.Required.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 8eb1d1c..3fac3b2 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -105,6 +105,34 @@ B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; }; B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; }; B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; }; + B509D7BA23C846E300F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; }; + B509D7BC23C847BC00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; }; + B509D7BD23C8480A00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; }; + B509D7BE23C8480A00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; }; + B509D7BF23C8480B00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; }; + B509D7C023C8480B00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; }; + B509D7C123C8480B00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; }; + B509D7C223C8480B00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; }; + B509D7C423C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; }; + B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; }; + B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; }; + B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; }; + B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; }; + B509D7CA23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; }; + B509D7CB23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; }; + B509D7CC23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; }; + B509D7CE23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; }; + B509D7CF23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; }; + B509D7D023C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; }; + B509D7D123C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; }; + B509D7D323C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; }; + B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; }; + B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; }; + B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; }; + B509D7D823C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; }; + B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; }; + B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; }; + B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; }; B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; }; B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; }; B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; }; @@ -905,6 +933,13 @@ B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; B50564D22350CC3100482308 /* PropertyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyProtocol.swift; sourceTree = ""; }; B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableAttributeType.swift; sourceTree = ""; }; + B509D7B923C846E300F42824 /* Value.Required.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Value.Required.swift; sourceTree = ""; }; + B509D7BB23C847BC00F42824 /* Value.Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Value.Optional.swift; sourceTree = ""; }; + B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.ToOne.swift; sourceTree = ""; }; + B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.ToManyOrdered.swift; sourceTree = ""; }; + B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.ToManyUnordered.swift; sourceTree = ""; }; + B509D7D223C84E1900F42824 /* Transformable.Required.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.Required.swift; sourceTree = ""; }; + B509D7D723C84E2600F42824 /* Transformable.Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.Optional.swift; sourceTree = ""; }; B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.StagedChangeset.swift; sourceTree = ""; }; B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.Changeset.swift; sourceTree = ""; }; B50E175623517DE4004F033C /* Differentiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Differentiable.swift; sourceTree = ""; }; @@ -1498,8 +1533,15 @@ isa = PBXGroup; children = ( B5D33A001E96012400C880DE /* Relationship.swift */, + B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */, + B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */, + B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */, B5D339E11E948C3600C880DE /* Value.swift */, + B509D7B923C846E300F42824 /* Value.Required.swift */, + B509D7BB23C847BC00F42824 /* Value.Optional.swift */, B5831B791F34ACBA00A9F647 /* Transformable.swift */, + B509D7D223C84E1900F42824 /* Transformable.Required.swift */, + B509D7D723C84E2600F42824 /* Transformable.Optional.swift */, ); name = Properties; sourceTree = ""; @@ -2103,6 +2145,7 @@ files = ( B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */, B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */, + B509D7C423C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */, B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, B5CA2B081F7E5ACA004B1936 /* WhereClauseType.swift in Sources */, @@ -2139,6 +2182,7 @@ B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */, B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */, B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */, + B509D7BC23C847BC00F42824 /* Value.Optional.swift in Sources */, B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, @@ -2174,8 +2218,10 @@ B5E84F111AFF847B0064E85B /* Select.swift in Sources */, B51260931E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, B5DAFB482203D9F8003FCCD0 /* Where.Expression.swift in Sources */, + B509D7D823C84E2600F42824 /* Transformable.Optional.swift in Sources */, B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */, B5BF7FAD234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift in Sources */, + B509D7BA23C846E300F42824 /* Value.Required.swift in Sources */, B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D21C991B3E00B5CEFA /* CSDataStack.swift in Sources */, B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */, @@ -2194,6 +2240,7 @@ B5A1DAC81F111BFA003CF369 /* KeyPath+Querying.swift in Sources */, 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */, B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */, + B509D7D323C84E1900F42824 /* Transformable.Required.swift in Sources */, B56923C41EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */, B5E84F411AFF8CCD0064E85B /* TypeErasedClauses.swift in Sources */, B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, @@ -2251,6 +2298,7 @@ B538BA771D15B3E30003A766 /* CoreStoreBridge.m in Sources */, B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */, B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */, + B509D7CE23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */, B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */, B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */, B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */, @@ -2274,6 +2322,7 @@ B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */, + B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B5277672234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, @@ -2341,6 +2390,7 @@ 82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */, B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */, B5D339D91E9489AB00C880DE /* CoreStoreObject.swift in Sources */, + B509D7CF23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */, 82BA18CE1C4BBD7100A0916E /* Internals.FetchedResultsControllerDelegate.swift in Sources */, B56923FB1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */, B55514EB1EED8BF900BAB888 /* From+Querying.swift in Sources */, @@ -2383,6 +2433,7 @@ B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, B55BB4DA23503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */, B5E1B5951CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */, + B509D7CA23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */, 18166887232B9ED60097C275 /* String+KeyPaths.swift in Sources */, B5ECDC2B1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, B56923F11EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */, @@ -2443,6 +2494,7 @@ 82BA18C31C4BBD5300A0916E /* ObjectObserver.swift in Sources */, 82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */, B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, + B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */, 82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */, 82BA18C71C4BBD5900A0916E /* CoreStore+Migration.swift in Sources */, B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, @@ -2459,6 +2511,7 @@ 82BA18D81C4BBD7100A0916E /* Internals.WeakObject.swift in Sources */, B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, + B509D7BD23C8480A00F42824 /* Value.Required.swift in Sources */, B5215CA51FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */, B5D33A021E96012400C880DE /* Relationship.swift in Sources */, B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */, @@ -2479,6 +2532,7 @@ 82BA18AD1C4BBD3100A0916E /* UnsafeDataTransaction.swift in Sources */, B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, B5277673234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, + B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */, B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */, 82BA18A81C4BBD2900A0916E /* CoreStoreLogger.swift in Sources */, B549F65F1E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */, @@ -2501,6 +2555,7 @@ 82BA18B91C4BBD4A00A0916E /* From.swift in Sources */, B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */, 82BA18BE1C4BBD4A00A0916E /* Tweak.swift in Sources */, + B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, @@ -2511,6 +2566,7 @@ B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */, 82BA18C01C4BBD5300A0916E /* DataStack+Observing.swift in Sources */, 82BA18A61C4BBD2900A0916E /* DefaultLogger.swift in Sources */, + B509D7BE23C8480A00F42824 /* Value.Optional.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2567,6 +2623,7 @@ B5ECDC151CA816E500C7F112 /* CSTweak.swift in Sources */, B546F9761C9C553300D5AC55 /* SetupResult.swift in Sources */, B53FBA161CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, + B509D7D123C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */, B5ECDC271CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */, B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */, B5D339DB1E9489AB00C880DE /* CoreStoreObject.swift in Sources */, @@ -2609,6 +2666,7 @@ B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */, B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */, B50EE14523473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, + B509D7CC23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */, B55BB4D823503B9500C33E34 /* EnvironmentValues+DataSources.swift in Sources */, B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */, 18166889232B9ED80097C275 /* String+KeyPaths.swift in Sources */, @@ -2669,6 +2727,7 @@ B52DD1BA1BE1F94000949AFE /* MigrationChain.swift in Sources */, B50392FB1C479640009900CA /* NSManagedObject+Transaction.swift in Sources */, B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, + B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */, B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */, B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */, @@ -2685,6 +2744,7 @@ B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, + B509D7C123C8480B00F42824 /* Value.Required.swift in Sources */, B5215CA71FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */, B5D33A041E96012400C880DE /* Relationship.swift in Sources */, B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */, @@ -2705,6 +2765,7 @@ B52DD1A01BE1F92C00949AFE /* UnsafeDataTransaction.swift in Sources */, B5ECDC331CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */, + B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */, B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */, B5277675234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */, @@ -2727,6 +2788,7 @@ B56923EF1EB827F6007C4DC9 /* SchemaMappingProvider.swift in Sources */, B53FBA081CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */, + B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5519A5C1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */, B5DBE2D51C991B3E00B5CEFA /* CSDataStack.swift in Sources */, B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, @@ -2737,6 +2799,7 @@ B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */, B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B509D7C223C8480B00F42824 /* Value.Optional.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2793,6 +2856,7 @@ B56321961BD65216006C9394 /* From.swift in Sources */, B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */, B5D339DA1E9489AB00C880DE /* CoreStoreObject.swift in Sources */, + B509D7D023C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */, B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */, B56923FC1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */, B55514EC1EED8BF900BAB888 /* From+Querying.swift in Sources */, @@ -2835,6 +2899,7 @@ B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, B55BB4D923503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */, B5ECDC2C1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, + B509D7CB23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */, 18166888232B9ED70097C275 /* String+KeyPaths.swift in Sources */, B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */, B56923F21EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */, @@ -2895,6 +2960,7 @@ B563219B1BD65216006C9394 /* Tweak.swift in Sources */, B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */, + B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */, B56321991BD65216006C9394 /* OrderBy.swift in Sources */, B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */, @@ -2911,6 +2977,7 @@ B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */, B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */, B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, + B509D7BF23C8480B00F42824 /* Value.Required.swift in Sources */, B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, B5215CA61FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */, B5D33A031E96012400C880DE /* Relationship.swift in Sources */, @@ -2931,6 +2998,7 @@ B563218D1BD65216006C9394 /* CoreStore+Transaction.swift in Sources */, B546F96B1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, B5277674234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, + B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */, B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */, B563218B1BD65216006C9394 /* UnsafeDataTransaction.swift in Sources */, B549F6601E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */, @@ -2953,6 +3021,7 @@ B56321861BD65216006C9394 /* CoreStoreLogger.swift in Sources */, B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */, + B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, @@ -2963,6 +3032,7 @@ B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */, B56321A01BD65216006C9394 /* ObjectObserver.swift in Sources */, B56321951BD65216006C9394 /* TypeErasedClauses.swift in Sources */, + B509D7C023C8480B00F42824 /* Value.Optional.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/Relationship.ToManyOrdered.swift b/Sources/Relationship.ToManyOrdered.swift new file mode 100644 index 0000000..069eb0a --- /dev/null +++ b/Sources/Relationship.ToManyOrdered.swift @@ -0,0 +1,459 @@ +// +// Relationship.ToManyOrdered.swift +// CoreStore +// +// Copyright © 2020 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: - RelationshipContainer + +extension RelationshipContainer { + + // MARK: - ToManyOrdered + + /** + The containing type for to-many ordered relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - Important: `Relationship.ToManyOrdered` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class ToManyOrdered: ToManyRelationshipKeyPathStringConvertible, RelationshipProtocol { + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { nil }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (D) -> RelationshipContainer.ToOne, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + minCount: Int = 0, + maxCount: Int = 0, + inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + minCount: minCount, + maxCount: maxCount, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + The relationship value + */ + public var value: ReturnValueType { + + get { + + return self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) + } + set { + + self.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! })) + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = D + + + // MARK: RelationshipKeyPathStringConvertible + + public typealias ReturnValueType = [DestinationValueType] + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: RelationshipProtocol + + internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal var nativeValue: NSOrderedSet { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + return object.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! NSOrderedSet?) ?? [] } + ) + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + object.setValue( + newValue, + forKvcKey: self.keyPath + ) + } + } + } + + internal var valueForSnapshot: Any? { + + return self.value.map({ $0.objectID() }) + } + + + // MARK: Private + + private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + let range = (Swift.max(0, minCount) ... maxCount) + return ( + isToMany: true, + isOrdered: true, + deleteRule: deleteRule.nativeValue, + inverse: (type: D.self, keyPath: inverseKeyPath()), + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + minCount: range.lowerBound, + maxCount: range.upperBound + ) + } + } + } +} + + +// MARK: - Convenience + +extension RelationshipContainer.ToManyOrdered: RandomAccessCollection { + + // MARK: Sequence + + public typealias Iterator = AnyIterator + + public func makeIterator() -> Iterator { + + var iterator = self.nativeValue.makeIterator() + return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) }) + } + + + // MARK: Collection + + public typealias Index = Int + + public var startIndex: Index { + + return 0 + } + + public var endIndex: Index { + + return self.nativeValue.count + } + + public subscript(position: Index) -> Iterator.Element { + + return D.cs_fromRaw(object: self.nativeValue[position] as! NSManagedObject) + } + + public func index(after i: Index) -> Index { + + return i + 1 + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension RelationshipContainer.ToManyOrdered { + + /** + Assigns a sequence of objects to the relationship. The operation + ``` + person.pets .= [dog, cat] + ``` + is equivalent to + ``` + person.pets.value = [dog, cat] + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ newValue: S) where S.Iterator.Element == D { + + relationship.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! })) + } + + /** + Assigns a sequence of objects to the relationship. The operation + ``` + person.pets .= anotherPerson.pets + ``` + is equivalent to + ``` + person.pets.value = anotherPerson.pets.value + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) { + + relationship.nativeValue = relationship2.nativeValue + } + + /** + Compares equality between a relationship's objects and a collection of objects + ``` + if person.pets .== [dog, cat] { ... } + ``` + is equivalent to + ``` + if person.pets.value == [dog, cat] { ... } + ``` + */ + public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ collection: C) -> Bool where C.Iterator.Element == D { + + return relationship.nativeValue.elementsEqual( + collection.lazy.map({ $0.rawObject! }), + by: { ($0 as! NSManagedObject) == $1 } + ) + } + + /** + Compares equality between a collection of objects and a relationship's objects + ``` + if [dog, cat] .== person.pets { ... } + ``` + is equivalent to + ``` + if [dog, cat] == person.pets.value { ... } + ``` + */ + public static func .== (_ collection: C, _ relationship: RelationshipContainer.ToManyOrdered) -> Bool where C.Iterator.Element == D { + + return relationship.nativeValue.elementsEqual( + collection.lazy.map({ $0.rawObject! }), + by: { ($0 as! NSManagedObject) == $1 } + ) + } + + /** + Compares equality between a relationship's objects and a collection of objects + ``` + if person.pets .== anotherPerson.pets { ... } + ``` + is equivalent to + ``` + if person.pets.value == anotherPerson.pets.value { ... } + ``` + */ + public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) -> Bool { + + return relationship.nativeValue == relationship2.nativeValue + } +} diff --git a/Sources/Relationship.ToManyUnordered.swift b/Sources/Relationship.ToManyUnordered.swift new file mode 100644 index 0000000..c45471d --- /dev/null +++ b/Sources/Relationship.ToManyUnordered.swift @@ -0,0 +1,461 @@ +// +// Relationship.ToManyUnordered.swift +// CoreStore +// +// Copyright © 2020 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: - RelationshipContainer + +extension RelationshipContainer { + + // MARK: - ToManyUnordered + + /** + The containing type for to-many unordered relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + } + ``` + - Important: `Relationship.ToManyUnordered` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class ToManyUnordered: ToManyRelationshipKeyPathStringConvertible, RelationshipProtocol { + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { nil }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + inverse: @escaping (D) -> RelationshipContainer.ToOne, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, + deleteRule: DeleteRule = .nullify, + minCount: Int = 0, + maxCount: Int = 0, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + minCount: minCount, + maxCount: maxCount, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + The relationship value + */ + public var value: ReturnValueType { + + get { + + return Set(self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) + } + set { + + self.nativeValue = NSSet(array: newValue.map({ $0.rawObject! })) + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = D + + + // MARK: RelationshipKeyPathStringConvertible + + public typealias ReturnValueType = Set + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: RelationshipProtocol + + internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal var nativeValue: NSSet { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + return object.getValue( + forKvcKey: self.keyPath, + didGetValue: { ($0 as! NSSet?) ?? [] } + ) + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + object.setValue( + newValue, + forKvcKey: self.keyPath + ) + } + } + } + + internal var valueForSnapshot: Any? { + + return Set(self.value.map({ $0.objectID() })) + } + + + // MARK: Private + + private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + let range = (Swift.max(0, minCount) ... maxCount) + return ( + isToMany: true, + isOrdered: false, + deleteRule: deleteRule.nativeValue, + inverse: (type: D.self, keyPath: inverseKeyPath()), + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + minCount: range.lowerBound, + maxCount: range.upperBound + ) + } + } + } +} + + +// MARK: - Convenience + +extension RelationshipContainer.ToManyUnordered: Sequence { + + /** + The number of elements in the set. + */ + public var count: Int { + + return self.nativeValue.count + } + + /** + A Boolean value indicating whether the range contains no elements. + */ + public var isEmpty: Bool { + + return self.nativeValue.count == 0 + } + + + // MARK: Sequence + + public typealias Iterator = AnyIterator + + public func makeIterator() -> Iterator { + + var iterator = self.nativeValue.makeIterator() + return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) }) + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension RelationshipContainer.ToManyUnordered { + + /** + Assigns a sequence of objects to the relationship. The operation + ``` + person.pets .= [dog, cat] + ``` + is equivalent to + ``` + person.pets.value = [dog, cat] + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ newValue: S) where S.Iterator.Element == D { + + relationship.nativeValue = NSSet(array: newValue.map({ $0.rawObject! })) + } + + /** + Assigns a sequence of objects to the relationship. The operation + ``` + person.pets .= anotherPerson.pets + ``` + is equivalent to + ``` + person.pets.value = anotherPerson.pets.value + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) { + + relationship.nativeValue = relationship2.nativeValue + } + + /** + Assigns a sequence of objects to the relationship. The operation + ``` + person.pets .= anotherPerson.pets + ``` + is equivalent to + ``` + person.pets.value = anotherPerson.pets.value + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyOrdered) { + + relationship.nativeValue = NSSet(set: relationship2.nativeValue.set) + } + + /** + Compares the if the relationship's objects and a set of objects have the same elements. + ``` + if person.pets .== Set([dog, cat]) { ... } + ``` + is equivalent to + ``` + if person.pets.value == Set([dog, cat]) { ... } + ``` + */ + public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ set: Set) -> Bool { + + return relationship.nativeValue.isEqual(to: Set(set.map({ $0.rawObject! }))) + } + + /** + Compares if a set of objects and a relationship's objects have the same elements. + ``` + if Set([dog, cat]) .== person.pets { ... } + ``` + is equivalent to + ``` + if Set([dog, cat]) == person.pets.value { ... } + ``` + */ + public static func .== (_ set: Set, _ relationship: RelationshipContainer.ToManyUnordered) -> Bool { + + return relationship.nativeValue.isEqual(to: Set(set.map({ $0.rawObject! }))) + } + + /** + Compares if a relationship's objects and another relationship's objects have the same elements. + ``` + if person.pets .== anotherPerson.pets { ... } + ``` + is equivalent to + ``` + if person.pets.value == anotherPerson.pets.value { ... } + ``` + */ + public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) -> Bool { + + return relationship.nativeValue.isEqual(relationship2.nativeValue) + } +} diff --git a/Sources/Relationship.ToOne.swift b/Sources/Relationship.ToOne.swift new file mode 100644 index 0000000..945eaa1 --- /dev/null +++ b/Sources/Relationship.ToOne.swift @@ -0,0 +1,387 @@ +// +// Relationship.ToOne.swift +// CoreStore +// +// Copyright © 2020 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: - RelationshipContainer + +extension RelationshipContainer { + + // MARK: - ToOne + + /** + The containing type for to-one relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + } + ``` + - Important: `Relationship.ToOne` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class ToOne: RelationshipKeyPathStringConvertible, RelationshipProtocol { + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { nil }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + inverse: @escaping (D) -> RelationshipContainer.ToOne, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. + ``` + class Dog: CoreStoreObject { + let master = Relationship.ToOne("master") + } + class Person: CoreStoreObject { + let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) + } + ``` + - parameter keyPath: the permanent name for this relationship. + - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. + - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. + - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public convenience init( + _ keyPath: KeyPathString, + inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, + deleteRule: DeleteRule = .nullify, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.init( + keyPath: keyPath, + inverseKeyPath: { inverse(D.meta).keyPath }, + deleteRule: deleteRule, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths() + ) + } + + /** + The relationship value + */ + public var value: ReturnValueType { + + get { + + return self.nativeValue.flatMap(D.cs_fromRaw) + } + set { + + self.nativeValue = newValue?.rawObject + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = D + + + // MARK: RelationshipKeyPathStringConvertible + + public typealias ReturnValueType = DestinationValueType? + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: RelationshipProtocol + + internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal var nativeValue: NSManagedObject? { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + return object.getValue( + forKvcKey: self.keyPath, + didGetValue: { $0 as! NSManagedObject? } + ) + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + object.setValue( + newValue, + forKvcKey: self.keyPath + ) + } + } + } + + internal var valueForSnapshot: Any? { + + return self.value?.objectID() + } + + + // MARK: Private + + private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: RelationshipContainer.DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + isToMany: false, + isOrdered: false, + deleteRule: deleteRule.nativeValue, + inverse: (type: D.self, keyPath: inverseKeyPath()), + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + minCount: 0, + maxCount: 1 + ) + } + } + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension RelationshipContainer.ToOne { + + /** + Assigns an object to the relationship. The operation + ``` + dog.master .= person + ``` + is equivalent to + ``` + dog.master.value = person + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToOne, _ newObject: D?) { + + relationship.nativeValue = newObject?.cs_toRaw() + } + + /** + Assigns an object from another relationship. The operation + ``` + dog.master .= anotherDog.master + ``` + is equivalent to + ``` + dog.master.value = anotherDog.master.value + ``` + */ + public static func .= (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) { + + relationship.nativeValue = relationship2.nativeValue + } + + /** + Compares equality between a relationship's object and another object + ``` + if dog.master .== person { ... } + ``` + is equivalent to + ``` + if dog.master.value == person { ... } + ``` + */ + public static func .== (_ relationship: RelationshipContainer.ToOne, _ object: D?) -> Bool { + + return relationship.nativeValue == object?.cs_toRaw() + } + + /** + Compares equality between an object and a relationship's object + ``` + if dog.master .== person { ... } + ``` + is equivalent to + ``` + if dog.master.value == person { ... } + ``` + */ + public static func .== (_ object: D?, _ relationship: RelationshipContainer.ToOne) -> Bool { + + return object?.cs_toRaw() == relationship.nativeValue + } + + /** + Compares equality between a relationship's object and another relationship's object + ``` + if dog.master .== person { ... } + ``` + is equivalent to + ``` + if dog.master.value == person { ... } + ``` + */ + public static func .== (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) -> Bool { + + return relationship.nativeValue == relationship2.nativeValue + } +} diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index 95946c0..212ea09 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -62,879 +62,20 @@ extension DynamicObject where Self: CoreStoreObject { */ public enum RelationshipContainer { - // MARK: - ToOne - - /** - The containing type for to-one relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - } - ``` - - Important: `Relationship.ToOne` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class ToOne: RelationshipKeyPathStringConvertible, RelationshipProtocol { - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { nil }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - inverse: @escaping (D) -> RelationshipContainer.ToOne, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - The relationship value - */ - public var value: ReturnValueType { - - get { - - return self.nativeValue.flatMap(D.cs_fromRaw) - } - set { - - self.nativeValue = newValue?.rawObject - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = D - - - // MARK: RelationshipKeyPathStringConvertible - - public typealias ReturnValueType = DestinationValueType? - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: RelationshipProtocol - - internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal var nativeValue: NSManagedObject? { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - return object.getValue( - forKvcKey: self.keyPath, - didGetValue: { $0 as! NSManagedObject? } - ) - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - object.setValue( - newValue, - forKvcKey: self.keyPath - ) - } - } - } - - internal var valueForSnapshot: Any? { - - return self.value?.objectID() - } - - - // MARK: Private - - private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - ( - isToMany: false, - isOrdered: false, - deleteRule: deleteRule.nativeValue, - inverse: (type: D.self, keyPath: inverseKeyPath()), - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - minCount: 0, - maxCount: 1 - ) - } - } - } - - - // MARK: - ToManyOrdered - - /** - The containing type for to-many ordered relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - Important: `Relationship.ToManyOrdered` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class ToManyOrdered: ToManyRelationshipKeyPathStringConvertible, RelationshipProtocol { - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - minCount: Int = 0, - maxCount: Int = 0, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - minCount: minCount, - maxCount: maxCount, - inverseKeyPath: { nil }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - minCount: Int = 0, - maxCount: Int = 0, - inverse: @escaping (D) -> RelationshipContainer.ToOne, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - minCount: minCount, - maxCount: maxCount, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - minCount: Int = 0, - maxCount: Int = 0, - inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - minCount: minCount, - maxCount: maxCount, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - minCount: Int = 0, - maxCount: Int = 0, - inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, - deleteRule: DeleteRule = .nullify, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - minCount: minCount, - maxCount: maxCount, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - The relationship value - */ - public var value: ReturnValueType { - - get { - - return self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) - } - set { - - self.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! })) - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = D - - - // MARK: RelationshipKeyPathStringConvertible - - public typealias ReturnValueType = [DestinationValueType] - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: RelationshipProtocol - - internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal var nativeValue: NSOrderedSet { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - return object.getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! NSOrderedSet?) ?? [] } - ) - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - object.setValue( - newValue, - forKvcKey: self.keyPath - ) - } - } - } - - internal var valueForSnapshot: Any? { - - return self.value.map({ $0.objectID() }) - } - - - // MARK: Private - - private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - let range = (Swift.max(0, minCount) ... maxCount) - return ( - isToMany: true, - isOrdered: true, - deleteRule: deleteRule.nativeValue, - inverse: (type: D.self, keyPath: inverseKeyPath()), - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - minCount: range.lowerBound, - maxCount: range.upperBound - ) - } - } - } - - - // MARK: - ToManyUnordered - - /** - The containing type for to-many unordered relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) - } - ``` - - Important: `Relationship.ToManyUnordered` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class ToManyUnordered: ToManyRelationshipKeyPathStringConvertible, RelationshipProtocol { - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - deleteRule: DeleteRule = .nullify, - minCount: Int = 0, - maxCount: Int = 0, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { nil }, - deleteRule: deleteRule, - minCount: minCount, - maxCount: maxCount, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - inverse: @escaping (D) -> RelationshipContainer.ToOne, - deleteRule: DeleteRule = .nullify, - minCount: Int = 0, - maxCount: Int = 0, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - minCount: minCount, - maxCount: maxCount, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - inverse: @escaping (D) -> RelationshipContainer.ToManyOrdered, - deleteRule: DeleteRule = .nullify, - minCount: Int = 0, - maxCount: Int = 0, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - minCount: minCount, - maxCount: maxCount, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument. - ``` - class Dog: CoreStoreObject { - let master = Relationship.ToOne("master") - } - class Person: CoreStoreObject { - let pets = Relationship.ToManyOrdered("pets", inverse: { $0.master }) - } - ``` - - parameter keyPath: the permanent name for this relationship. - - parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error. - - parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. - - parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`. - - parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public convenience init( - _ keyPath: KeyPathString, - inverse: @escaping (D) -> RelationshipContainer.ToManyUnordered, - deleteRule: DeleteRule = .nullify, - minCount: Int = 0, - maxCount: Int = 0, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.init( - keyPath: keyPath, - inverseKeyPath: { inverse(D.meta).keyPath }, - deleteRule: deleteRule, - minCount: minCount, - maxCount: maxCount, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths() - ) - } - - /** - The relationship value - */ - public var value: ReturnValueType { - - get { - - return Set(self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })) - } - set { - - self.nativeValue = NSSet(array: newValue.map({ $0.rawObject! })) - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = D - - - // MARK: RelationshipKeyPathStringConvertible - - public typealias ReturnValueType = Set - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: RelationshipProtocol - - internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal var nativeValue: NSSet { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - return object.getValue( - forKvcKey: self.keyPath, - didGetValue: { ($0 as! NSSet?) ?? [] } - ) - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - object.setValue( - newValue, - forKvcKey: self.keyPath - ) - } - } - } - - internal var valueForSnapshot: Any? { - - return Set(self.value.map({ $0.objectID() })) - } - - - // MARK: Private - - private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - let range = (Swift.max(0, minCount) ... maxCount) - return ( - isToMany: true, - isOrdered: false, - deleteRule: deleteRule.nativeValue, - inverse: (type: D.self, keyPath: inverseKeyPath()), - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - minCount: range.lowerBound, - maxCount: range.upperBound - ) - } - } - } - - // MARK: - DeleteRule public enum DeleteRule { + + // MARK: Public case nullify case cascade case deny + + + // MARK: Internal - fileprivate var nativeValue: NSDeleteRule { + internal var nativeValue: NSDeleteRule { switch self { @@ -945,335 +86,3 @@ public enum RelationshipContainer { } } } - - -// MARK: - Convenience - -extension RelationshipContainer.ToManyOrdered: RandomAccessCollection { - - // MARK: Sequence - - public typealias Iterator = AnyIterator - - public func makeIterator() -> Iterator { - - var iterator = self.nativeValue.makeIterator() - return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) }) - } - - - // MARK: Collection - - public typealias Index = Int - - public var startIndex: Index { - - return 0 - } - - public var endIndex: Index { - - return self.nativeValue.count - } - - public subscript(position: Index) -> Iterator.Element { - - return D.cs_fromRaw(object: self.nativeValue[position] as! NSManagedObject) - } - - public func index(after i: Index) -> Index { - - return i + 1 - } -} - -extension RelationshipContainer.ToManyUnordered: Sequence { - - /** - The number of elements in the set. - */ - public var count: Int { - - return self.nativeValue.count - } - - /** - A Boolean value indicating whether the range contains no elements. - */ - public var isEmpty: Bool { - - return self.nativeValue.count == 0 - } - - - // MARK: Sequence - - public typealias Iterator = AnyIterator - - public func makeIterator() -> Iterator { - - var iterator = self.nativeValue.makeIterator() - return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) }) - } -} - - -// MARK: - Operations - -infix operator .= : AssignmentPrecedence -infix operator .== : ComparisonPrecedence - -extension RelationshipContainer.ToOne { - - /** - Assigns an object to the relationship. The operation - ``` - dog.master .= person - ``` - is equivalent to - ``` - dog.master.value = person - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToOne, _ newObject: D?) { - - relationship.nativeValue = newObject?.cs_toRaw() - } - - /** - Assigns an object from another relationship. The operation - ``` - dog.master .= anotherDog.master - ``` - is equivalent to - ``` - dog.master.value = anotherDog.master.value - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) { - - relationship.nativeValue = relationship2.nativeValue - } - - /** - Compares equality between a relationship's object and another object - ``` - if dog.master .== person { ... } - ``` - is equivalent to - ``` - if dog.master.value == person { ... } - ``` - */ - public static func .== (_ relationship: RelationshipContainer.ToOne, _ object: D?) -> Bool { - - return relationship.nativeValue == object?.cs_toRaw() - } - - /** - Compares equality between an object and a relationship's object - ``` - if dog.master .== person { ... } - ``` - is equivalent to - ``` - if dog.master.value == person { ... } - ``` - */ - public static func .== (_ object: D?, _ relationship: RelationshipContainer.ToOne) -> Bool { - - return object?.cs_toRaw() == relationship.nativeValue - } - - /** - Compares equality between a relationship's object and another relationship's object - ``` - if dog.master .== person { ... } - ``` - is equivalent to - ``` - if dog.master.value == person { ... } - ``` - */ - public static func .== (_ relationship: RelationshipContainer.ToOne, _ relationship2: RelationshipContainer.ToOne) -> Bool { - - return relationship.nativeValue == relationship2.nativeValue - } -} - -extension RelationshipContainer.ToManyOrdered { - - /** - Assigns a sequence of objects to the relationship. The operation - ``` - person.pets .= [dog, cat] - ``` - is equivalent to - ``` - person.pets.value = [dog, cat] - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ newValue: S) where S.Iterator.Element == D { - - relationship.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! })) - } - - /** - Assigns a sequence of objects to the relationship. The operation - ``` - person.pets .= anotherPerson.pets - ``` - is equivalent to - ``` - person.pets.value = anotherPerson.pets.value - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) { - - relationship.nativeValue = relationship2.nativeValue - } - - /** - Compares equality between a relationship's objects and a collection of objects - ``` - if person.pets .== [dog, cat] { ... } - ``` - is equivalent to - ``` - if person.pets.value == [dog, cat] { ... } - ``` - */ - public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ collection: C) -> Bool where C.Iterator.Element == D { - - return relationship.nativeValue.elementsEqual( - collection.lazy.map({ $0.rawObject! }), - by: { ($0 as! NSManagedObject) == $1 } - ) - } - - /** - Compares equality between a collection of objects and a relationship's objects - ``` - if [dog, cat] .== person.pets { ... } - ``` - is equivalent to - ``` - if [dog, cat] == person.pets.value { ... } - ``` - */ - public static func .== (_ collection: C, _ relationship: RelationshipContainer.ToManyOrdered) -> Bool where C.Iterator.Element == D { - - return relationship.nativeValue.elementsEqual( - collection.lazy.map({ $0.rawObject! }), - by: { ($0 as! NSManagedObject) == $1 } - ) - } - - /** - Compares equality between a relationship's objects and a collection of objects - ``` - if person.pets .== anotherPerson.pets { ... } - ``` - is equivalent to - ``` - if person.pets.value == anotherPerson.pets.value { ... } - ``` - */ - public static func .== (_ relationship: RelationshipContainer.ToManyOrdered, _ relationship2: RelationshipContainer.ToManyOrdered) -> Bool { - - return relationship.nativeValue == relationship2.nativeValue - } -} - -extension RelationshipContainer.ToManyUnordered { - - /** - Assigns a sequence of objects to the relationship. The operation - ``` - person.pets .= [dog, cat] - ``` - is equivalent to - ``` - person.pets.value = [dog, cat] - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ newValue: S) where S.Iterator.Element == D { - - relationship.nativeValue = NSSet(array: newValue.map({ $0.rawObject! })) - } - - /** - Assigns a sequence of objects to the relationship. The operation - ``` - person.pets .= anotherPerson.pets - ``` - is equivalent to - ``` - person.pets.value = anotherPerson.pets.value - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) { - - relationship.nativeValue = relationship2.nativeValue - } - - /** - Assigns a sequence of objects to the relationship. The operation - ``` - person.pets .= anotherPerson.pets - ``` - is equivalent to - ``` - person.pets.value = anotherPerson.pets.value - ``` - */ - public static func .= (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyOrdered) { - - relationship.nativeValue = NSSet(set: relationship2.nativeValue.set) - } - - /** - Compares the if the relationship's objects and a set of objects have the same elements. - ``` - if person.pets .== Set([dog, cat]) { ... } - ``` - is equivalent to - ``` - if person.pets.value == Set([dog, cat]) { ... } - ``` - */ - public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ set: Set) -> Bool { - - return relationship.nativeValue.isEqual(to: Set(set.map({ $0.rawObject! }))) - } - - /** - Compares if a set of objects and a relationship's objects have the same elements. - ``` - if Set([dog, cat]) .== person.pets { ... } - ``` - is equivalent to - ``` - if Set([dog, cat]) == person.pets.value { ... } - ``` - */ - public static func .== (_ set: Set, _ relationship: RelationshipContainer.ToManyUnordered) -> Bool { - - return relationship.nativeValue.isEqual(to: Set(set.map({ $0.rawObject! }))) - } - - /** - Compares if a relationship's objects and another relationship's objects have the same elements. - ``` - if person.pets .== anotherPerson.pets { ... } - ``` - is equivalent to - ``` - if person.pets.value == anotherPerson.pets.value { ... } - ``` - */ - public static func .== (_ relationship: RelationshipContainer.ToManyUnordered, _ relationship2: RelationshipContainer.ToManyUnordered) -> Bool { - - return relationship.nativeValue.isEqual(relationship2.nativeValue) - } -} diff --git a/Sources/Transformable.Optional.swift b/Sources/Transformable.Optional.swift new file mode 100644 index 0000000..fa647cb --- /dev/null +++ b/Sources/Transformable.Optional.swift @@ -0,0 +1,303 @@ +// +// Transformable.Optional.swift +// CoreStore +// +// Copyright © 2020 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: - TransformableContainer + +extension TransformableContainer { + + // MARK: - Optional + + /** + The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species", initial: "") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + - Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class Optional: AttributeKeyPathStringConvertible, AttributeProtocol { + + /** + Initializes the metadata for the property. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species", initial: "") + let color = Transformable.Optional( + "color", + isTransient: true, + customGetter: Animal.getColor(_:) + ) + } + + private static func getColor(_ partialObject: PartialObject) -> UIColor? { + if let cachedColor = partialObject.primitiveValue(for: { $0.color }) { + return cachedColor + } + let color: UIColor? + switch partialObject.value(for: { $0.species }) { + + case "Swift": color = UIColor.orange + case "Bulbasaur": color = UIColor.green + default: return nil + } + partialObject.setPrimitiveValue(color, for: { $0.color }) + return color + } + ``` + - parameter keyPath: the permanent attribute name for this property. + - parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public init( + _ keyPath: KeyPathString, + initial: @autoclosure @escaping () -> V? = nil, + isTransient: Bool = false, + allowsExternalBinaryDataStorage: Bool = false, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V?)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + attributeType: .transformableAttributeType, + isOptional: true, + isTransient: isTransient, + allowsExternalBinaryDataStorage: allowsExternalBinaryDataStorage, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: initial() + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + /** + The attribute value + */ + public var value: ReturnValueType { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object)) + } + return object.value(forKey: self.keyPath) as! V? + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object), newValue) + } + object.setValue( + newValue, + forKey: self.keyPath + ) + } + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = V + + + // MARK: AttributeKeyPathStringConvertible + + public typealias ReturnValueType = DestinationValueType? + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: AttributeProtocol + + internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in + + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value + } + } + + internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } + customSetter( + PartialObject(rawObject), + newValue as! V? + ) + } + } + + internal var valueForSnapshot: Any? { + + return self.value + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V?)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension TransformableContainer.Optional { + + /** + Assigns an optional transformable value to the property. The operation + ``` + animal.color .= UIColor.red + ``` + is equivalent to + ``` + animal.color.value = UIColor.red + ``` + */ + public static func .= (_ property: TransformableContainer.Optional, _ newValue: V?) { + + property.value = newValue + } + + /** + Assigns an optional transformable value from another property. The operation + ``` + animal.color .= anotherAnimal.color + ``` + is equivalent to + ``` + animal.color.value = anotherAnimal.color.value + ``` + */ + public static func .= (_ property: TransformableContainer.Optional, _ property2: TransformableContainer.Optional) { + + property.value = property2.value + } + + /** + Assigns a transformable value from another property. The operation + ``` + animal.color .= anotherAnimal.color + ``` + is equivalent to + ``` + animal.color.value = anotherAnimal.color.value + ``` + */ + public static func .= (_ property: TransformableContainer.Optional, _ property2: TransformableContainer.Required) { + + property.value = property2.value + } +} diff --git a/Sources/Transformable.Required.swift b/Sources/Transformable.Required.swift new file mode 100644 index 0000000..fdf4e5c --- /dev/null +++ b/Sources/Transformable.Required.swift @@ -0,0 +1,291 @@ +// +// Transformable.Required.swift +// CoreStore +// +// Copyright © 2020 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: - TransformableContainer + +extension TransformableContainer { + + // MARK: - Required + + /** + The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species", initial: "") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + - Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class Required: AttributeKeyPathStringConvertible, AttributeProtocol { + + /** + Initializes the metadata for the property. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species", initial: "") + let color = Transformable.Required( + "color", + initial: UIColor.clear, + isTransient: true, + customGetter: Animal.getColor(_:) + ) + } + + private static func getColor(_ partialObject: PartialObject) -> UIColor { + let cachedColor = partialObject.primitiveValue(for: { $0.color }) + if cachedColor != UIColor.clear { + + return cachedColor + } + let color: UIColor + switch partialObject.value(for: { $0.species }) { + + case "Swift": color = UIColor.orange + case "Bulbasaur": color = UIColor.green + default: color = UIColor.black + } + partialObject.setPrimitiveValue(color, for: { $0.color }) + return color + } + ``` + - parameter keyPath: the permanent attribute name for this property. + - parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public init( + _ keyPath: KeyPathString, + initial: @autoclosure @escaping () -> V, + isTransient: Bool = false, + allowsExternalBinaryDataStorage: Bool = false, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + attributeType: .transformableAttributeType, + isOptional: false, + isTransient: isTransient, + allowsExternalBinaryDataStorage: allowsExternalBinaryDataStorage, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: initial() + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + /** + The attribute value + */ + public var value: ReturnValueType { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object)) + } + return object.value(forKey: self.keyPath)! as! V + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object), newValue) + } + object.setValue( + newValue, + forKey: self.keyPath + ) + } + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = V + + + // MARK: AttributeKeyPathStringConvertible + + public typealias ReturnValueType = DestinationValueType + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: AttributeProtocol + + internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in + + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value + } + } + + internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } + customSetter( + PartialObject(rawObject), + newValue as! V + ) + } + } + + internal var valueForSnapshot: Any? { + + return self.value + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension TransformableContainer.Required { + + /** + Assigns a transformable value to the property. The operation + ``` + animal.color .= UIColor.red + ``` + is equivalent to + ``` + animal.color.value = UIColor.red + ``` + */ + public static func .= (_ property: TransformableContainer.Required, _ newValue: V) { + + property.value = newValue + } + + /** + Assigns a transformable value from another property. The operation + ``` + animal.nickname .= anotherAnimal.species + ``` + is equivalent to + ``` + animal.nickname.value = anotherAnimal.species.value + ``` + */ + public static func .= (_ property: TransformableContainer.Required, _ property2: TransformableContainer.Required) { + + property.value = property2.value + } +} diff --git a/Sources/Transformable.swift b/Sources/Transformable.swift index 33194f0..89eb9d9 100644 --- a/Sources/Transformable.swift +++ b/Sources/Transformable.swift @@ -58,528 +58,4 @@ extension DynamicObject where Self: CoreStoreObject { } ``` */ -public enum TransformableContainer { - - // MARK: - Required - - /** - The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported. - ``` - class Animal: CoreStoreObject { - let species = Value.Required("species", initial: "") - let nickname = Value.Optional("nickname") - let color = Transformable.Optional("color") - } - ``` - - Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class Required: AttributeKeyPathStringConvertible, AttributeProtocol { - - /** - Initializes the metadata for the property. - ``` - class Animal: CoreStoreObject { - let species = Value.Required("species", initial: "") - let color = Transformable.Required( - "color", - initial: UIColor.clear, - isTransient: true, - customGetter: Animal.getColor(_:) - ) - } - - private static func getColor(_ partialObject: PartialObject) -> UIColor { - let cachedColor = partialObject.primitiveValue(for: { $0.color }) - if cachedColor != UIColor.clear { - - return cachedColor - } - let color: UIColor - switch partialObject.value(for: { $0.species }) { - - case "Swift": color = UIColor.orange - case "Bulbasaur": color = UIColor.green - default: color = UIColor.black - } - partialObject.setPrimitiveValue(color, for: { $0.color }) - return color - } - ``` - - parameter keyPath: the permanent attribute name for this property. - - parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. - - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - - parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`. - - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. - - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public init( - _ keyPath: KeyPathString, - initial: @autoclosure @escaping () -> V, - isTransient: Bool = false, - allowsExternalBinaryDataStorage: Bool = false, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - customGetter: ((_ partialObject: PartialObject) -> V)? = nil, - customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - ( - attributeType: .transformableAttributeType, - isOptional: false, - isTransient: isTransient, - allowsExternalBinaryDataStorage: allowsExternalBinaryDataStorage, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - defaultValue: initial() - ) - } - self.customGetter = customGetter - self.customSetter = customSetter - } - - /** - The attribute value - */ - public var value: ReturnValueType { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - if let customGetter = self.customGetter { - - return customGetter(PartialObject(object)) - } - return object.value(forKey: self.keyPath)! as! V - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - if let customSetter = self.customSetter { - - return customSetter(PartialObject(object), newValue) - } - object.setValue( - newValue, - forKey: self.keyPath - ) - } - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = V - - - // MARK: AttributeKeyPathStringConvertible - - public typealias ReturnValueType = DestinationValueType - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: AttributeProtocol - - internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in - - guard let customGetter = self.customGetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any) -> Any? in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willAccessValue(forKey: keyPath) - defer { - - rawObject.didAccessValue(forKey: keyPath) - } - let value = customGetter(PartialObject(rawObject)) - return value - } - } - - internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in - - guard let customSetter = self.customSetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any, _ newValue: Any?) -> Void in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willChangeValue(forKey: keyPath) - defer { - - rawObject.didChangeValue(forKey: keyPath) - } - customSetter( - PartialObject(rawObject), - newValue as! V - ) - } - } - - internal var valueForSnapshot: Any? { - - return self.value - } - - - // MARK: Private - - private let customGetter: ((_ partialObject: PartialObject) -> V)? - private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? - } - - - // MARK: - Optional - - /** - The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported. - ``` - class Animal: CoreStoreObject { - let species = Value.Required("species", initial: "") - let nickname = Value.Optional("nickname") - let color = Transformable.Optional("color") - } - ``` - - Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class Optional: AttributeKeyPathStringConvertible, AttributeProtocol { - - /** - Initializes the metadata for the property. - ``` - class Animal: CoreStoreObject { - let species = Value.Required("species", initial: "") - let color = Transformable.Optional( - "color", - isTransient: true, - customGetter: Animal.getColor(_:) - ) - } - - private static func getColor(_ partialObject: PartialObject) -> UIColor? { - if let cachedColor = partialObject.primitiveValue(for: { $0.color }) { - return cachedColor - } - let color: UIColor? - switch partialObject.value(for: { $0.species }) { - - case "Swift": color = UIColor.orange - case "Bulbasaur": color = UIColor.green - default: return nil - } - partialObject.setPrimitiveValue(color, for: { $0.color }) - return color - } - ``` - - parameter keyPath: the permanent attribute name for this property. - - parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified. - - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - - parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`. - - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. - - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public init( - _ keyPath: KeyPathString, - initial: @autoclosure @escaping () -> V? = nil, - isTransient: Bool = false, - allowsExternalBinaryDataStorage: Bool = false, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - customGetter: ((_ partialObject: PartialObject) -> V?)? = nil, - customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - ( - attributeType: .transformableAttributeType, - isOptional: true, - isTransient: isTransient, - allowsExternalBinaryDataStorage: allowsExternalBinaryDataStorage, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - defaultValue: initial() - ) - } - self.customGetter = customGetter - self.customSetter = customSetter - } - - /** - The attribute value - */ - public var value: ReturnValueType { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - if let customGetter = self.customGetter { - - return customGetter(PartialObject(object)) - } - return object.value(forKey: self.keyPath) as! V? - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - if let customSetter = self.customSetter { - - return customSetter(PartialObject(object), newValue) - } - object.setValue( - newValue, - forKey: self.keyPath - ) - } - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = V - - - // MARK: AttributeKeyPathStringConvertible - - public typealias ReturnValueType = DestinationValueType? - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: AttributeProtocol - - internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in - - guard let customGetter = self.customGetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any) -> Any? in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willAccessValue(forKey: keyPath) - defer { - - rawObject.didAccessValue(forKey: keyPath) - } - let value = customGetter(PartialObject(rawObject)) - return value - } - } - - internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in - - guard let customSetter = self.customSetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any, _ newValue: Any?) -> Void in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willChangeValue(forKey: keyPath) - defer { - - rawObject.didChangeValue(forKey: keyPath) - } - customSetter( - PartialObject(rawObject), - newValue as! V? - ) - } - } - - internal var valueForSnapshot: Any? { - - return self.value - } - - - // MARK: Private - - private let customGetter: ((_ partialObject: PartialObject) -> V?)? - private let customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? - } -} - - -// MARK: - Operations - -infix operator .= : AssignmentPrecedence -infix operator .== : ComparisonPrecedence - -extension TransformableContainer.Required { - - /** - Assigns a transformable value to the property. The operation - ``` - animal.color .= UIColor.red - ``` - is equivalent to - ``` - animal.color.value = UIColor.red - ``` - */ - public static func .= (_ property: TransformableContainer.Required, _ newValue: V) { - - property.value = newValue - } - - /** - Assigns a transformable value from another property. The operation - ``` - animal.nickname .= anotherAnimal.species - ``` - is equivalent to - ``` - animal.nickname.value = anotherAnimal.species.value - ``` - */ - public static func .= (_ property: TransformableContainer.Required, _ property2: TransformableContainer.Required) { - - property.value = property2.value - } -} - -extension TransformableContainer.Optional { - - /** - Assigns an optional transformable value to the property. The operation - ``` - animal.color .= UIColor.red - ``` - is equivalent to - ``` - animal.color.value = UIColor.red - ``` - */ - public static func .= (_ property: TransformableContainer.Optional, _ newValue: V?) { - - property.value = newValue - } - - /** - Assigns an optional transformable value from another property. The operation - ``` - animal.color .= anotherAnimal.color - ``` - is equivalent to - ``` - animal.color.value = anotherAnimal.color.value - ``` - */ - public static func .= (_ property: TransformableContainer.Optional, _ property2: TransformableContainer.Optional) { - - property.value = property2.value - } - - /** - Assigns a transformable value from another property. The operation - ``` - animal.color .= anotherAnimal.color - ``` - is equivalent to - ``` - animal.color.value = anotherAnimal.color.value - ``` - */ - public static func .= (_ property: TransformableContainer.Optional, _ property2: TransformableContainer.Required) { - - property.value = property2.value - } -} +public enum TransformableContainer {} diff --git a/Sources/Value.Optional.swift b/Sources/Value.Optional.swift new file mode 100644 index 0000000..ec83d40 --- /dev/null +++ b/Sources/Value.Optional.swift @@ -0,0 +1,364 @@ +// +// Value.Optional.swift +// CoreStore +// +// Copyright © 2020 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: - ValueContainer + +extension ValueContainer { + + // MARK: - Optional + + /** + The containing type for optional value properties. Any type that conforms to `ImportableAttributeType` are supported. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species", initial: "") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + - Important: `Value.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class Optional: AttributeKeyPathStringConvertible, AttributeProtocol { + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + let title = Value.Optional("title", initial: "Mr.") + let name = Value.Optional("name") + let displayName = Value.Optional( + "displayName", + isTransient: true, + customGetter: Person.getName(_:) + ) + + private static func getName(_ partialObject: PartialObject) -> String? { + if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) { + return cachedDisplayName + } + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } + } + ``` + - parameter keyPath: the permanent attribute name for this property. + - parameter initial: the initial value for the property when the object is first created. Defaults to `nil` if not specified. + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. + - parameter self: the `CoreStoreObject` + - parameter getValue: the original getter for the property + - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. + - parameter setValue: the original setter for the property + - parameter finalNewValue: the transformed new value + - parameter originalNewValue: the original new value + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public init( + _ keyPath: KeyPathString, + initial: @autoclosure @escaping () -> V? = nil, + isTransient: Bool = false, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V?)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + attributeType: V.cs_rawAttributeType, + isOptional: true, + isTransient: isTransient, + allowsExternalBinaryDataStorage: false, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: initial()?.cs_toQueryableNativeType() + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + /** + The attribute value + */ + public var value: ReturnValueType { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object)) + } + return (object.value(forKey: self.keyPath) as! V.QueryableNativeType?) + .flatMap(V.cs_fromQueryableNativeType) + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object), newValue) + } + object.setValue( + newValue?.cs_toQueryableNativeType(), + forKey: self.keyPath + ) + } + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = V + + + // MARK: AttributeKeyPathStringConvertible + + public typealias ReturnValueType = DestinationValueType? + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: AttributeProtocol + + internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in + + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value?.cs_toQueryableNativeType() + } + } + + internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } + customSetter( + PartialObject(rawObject), + (newValue as! V.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType) + ) + } + } + + internal var valueForSnapshot: Any? { + + return self.value + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V?)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension ValueContainer.Optional { + + /** + Assigns an optional value to the property. The operation + ``` + animal.nickname .= "Taylor" + ``` + is equivalent to + ``` + animal.nickname.value = "Taylor" + ``` + */ + public static func .= (_ property: ValueContainer.Optional, _ newValue: V?) { + + property.value = newValue + } + + /** + Assigns an optional value from another property. The operation + ``` + animal.nickname .= anotherAnimal.nickname + ``` + is equivalent to + ``` + animal.nickname.value = anotherAnimal.nickname.value + ``` + */ + public static func .= (_ property: ValueContainer.Optional, _ property2: ValueContainer.Optional) { + + property.value = property2.value + } + + /** + Assigns a value from another property. The operation + ``` + animal.nickname .= anotherAnimal.species + ``` + is equivalent to + ``` + animal.nickname.value = anotherAnimal.species.value + ``` + */ + public static func .= (_ property: ValueContainer.Optional, _ property2: ValueContainer.Required) { + + property.value = property2.value + } + + /** + Compares equality between a property's value and another value + ``` + if animal.species .== "Swift" { ... } + ``` + is equivalent to + ``` + if animal.species.value == "Swift" { ... } + ``` + */ + public static func .== (_ property: ValueContainer.Optional, _ value: V?) -> Bool { + + return property.value == value + } + + /** + Compares equality between a property's value and another property's value + ``` + if "Swift" .== animal.species { ... } + ``` + is equivalent to + ``` + if "Swift" == animal.species.value { ... } + ``` + */ + public static func .== (_ value: V?, _ property: ValueContainer.Optional) -> Bool { + + return value == property.value + } + + /** + Compares equality between a property's value and another property's value + ``` + if animal.species .== anotherAnimal.species { ... } + ``` + is equivalent to + ``` + if animal.species.value == anotherAnimal.species.value { ... } + ``` + */ + public static func .== (_ property: ValueContainer.Optional, _ property2: ValueContainer.Optional) -> Bool { + + return property.value == property2.value + } + + /** + Compares equality between a property's value and another property's value + ``` + if animal.species .== anotherAnimal.species { ... } + ``` + is equivalent to + ``` + if animal.species.value == anotherAnimal.species.value { ... } + ``` + */ + public static func .== (_ property: ValueContainer.Optional, _ property2: ValueContainer.Required) -> Bool { + + return property.value == property2.value + } +} diff --git a/Sources/Value.Required.swift b/Sources/Value.Required.swift new file mode 100644 index 0000000..7c007e2 --- /dev/null +++ b/Sources/Value.Required.swift @@ -0,0 +1,347 @@ +// +// Value.Required.swift +// CoreStore +// +// Copyright © 2020 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: - ValueContainer + +extension ValueContainer { + + // MARK: - Required + + /** + The containing type for required value properties. Any type that conforms to `ImportableAttributeType` are supported. + ``` + class Animal: CoreStoreObject { + let species = Value.Required("species", initial: "") + let nickname = Value.Optional("nickname") + let color = Transformable.Optional("color") + } + ``` + - Important: `Value.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. + */ + public final class Required: AttributeKeyPathStringConvertible, AttributeProtocol { + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + let title = Value.Required("title", initial: "Mr.") + let name = Value.Required("name", initial: "") + let displayName = Value.Required( + "displayName", + initial: "", + isTransient: true, + customGetter: Person.getName(_:) + ) + + private static func getName(_ partialObject: PartialObject) -> String { + let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) + if !cachedDisplayName.isEmpty { + return cachedDisplayName + } + let title = partialObject.value(for: { $0.title }) + let name = partialObject.value(for: { $0.name }) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } + } + ``` + - parameter keyPath: the permanent attribute name for this property. + - parameter initial: the initial value for the property when the object is first created + - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. + - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) + - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. + - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. + - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. + - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. + */ + public init( + _ keyPath: KeyPathString, + initial: @autoclosure @escaping () -> V, + isTransient: Bool = false, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + renamingIdentifier: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + attributeType: V.cs_rawAttributeType, + isOptional: false, + isTransient: isTransient, + allowsExternalBinaryDataStorage: false, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: initial().cs_toQueryableNativeType() + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + /** + The attribute value + */ + public var value: ReturnValueType { + + get { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + if let customGetter = self.customGetter { + + return customGetter(PartialObject(object)) + } + return V.cs_fromQueryableNativeType( + object.value(forKey: self.keyPath)! as! V.QueryableNativeType + )! + } + } + set { + + Internals.assert( + self.rawObject != nil, + "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." + ) + return withExtendedLifetime(self.rawObject!) { (object) in + + Internals.assert( + object.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + object.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + if let customSetter = self.customSetter { + + return customSetter(PartialObject(object), newValue) + } + return object.setValue( + newValue.cs_toQueryableNativeType(), + forKey: self.keyPath + ) + } + } + } + + + // MARK: AnyKeyPathStringConvertible + + public var cs_keyPathString: String { + + return self.keyPath + } + + + // MARK: KeyPathStringConvertible + + public typealias ObjectType = O + public typealias DestinationValueType = V + + + // MARK: AttributeKeyPathStringConvertible + + public typealias ReturnValueType = DestinationValueType + + + // MARK: PropertyProtocol + + internal let keyPath: KeyPathString + + + // MARK: AttributeProtocol + + internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues + internal var rawObject: CoreStoreManagedObject? + + internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in + + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value.cs_toQueryableNativeType() + } + } + + internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in + + guard let customSetter = self.customSetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any, _ newValue: Any?) -> Void in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willChangeValue(forKey: keyPath) + defer { + + rawObject.didChangeValue(forKey: keyPath) + } + customSetter( + PartialObject(rawObject), + V.cs_fromQueryableNativeType(newValue as! V.QueryableNativeType)! + ) + } + } + + internal var valueForSnapshot: Any? { + + return self.value + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? + } +} + + +// MARK: - Operations + +infix operator .= : AssignmentPrecedence +infix operator .== : ComparisonPrecedence + +extension ValueContainer.Required { + + /** + Assigns a value to the property. The operation + ``` + animal.species .= "Swift" + ``` + is equivalent to + ``` + animal.species.value = "Swift" + ``` + */ + public static func .= (_ property: ValueContainer.Required, _ newValue: V) { + + property.value = newValue + } + + /** + Assigns a value from another property. The operation + ``` + animal.species .= anotherAnimal.species + ``` + is equivalent to + ``` + animal.species.value = anotherAnimal.species.value + ``` + */ + public static func .= (_ property: ValueContainer.Required, _ property2: ValueContainer.Required) { + + property.value = property2.value + } + + /** + Compares equality between a property's value and another value + ``` + if animal.species .== "Swift" { ... } + ``` + is equivalent to + ``` + if animal.species.value == "Swift" { ... } + ``` + */ + public static func .== (_ property: ValueContainer.Required, _ value: V?) -> Bool { + + return property.value == value + } + + /** + Compares equality between a value and a property's value + ``` + if "Swift" .== animal.species { ... } + ``` + is equivalent to + ``` + if "Swift" == animal.species.value { ... } + ``` + */ + public static func .== (_ value: V?, _ property: ValueContainer.Required) -> Bool { + + return value == property.value + } + + /** + Compares equality between a property's value and another property's value + ``` + if animal.species .== anotherAnimal.species { ... } + ``` + is equivalent to + ``` + if animal.species.value == anotherAnimal.species.value { ... } + ``` + */ + public static func .== (_ property: ValueContainer.Required, _ property2: ValueContainer.Required) -> Bool { + + return property.value == property2.value + } + + /** + Compares equality between a property's value and another property's value + ``` + if animal.species .== anotherAnimal.species { ... } + ``` + is equivalent to + ``` + if animal.species.value == anotherAnimal.species.value { ... } + ``` + */ + public static func .== (_ property: ValueContainer.Required, _ property2: ValueContainer.Optional) -> Bool { + + return property.value == property2.value + } +} diff --git a/Sources/Value.swift b/Sources/Value.swift index be33313..34df6bc 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -58,645 +58,4 @@ extension DynamicObject where Self: CoreStoreObject { } ``` */ -public enum ValueContainer { - - // MARK: - Required - - /** - The containing type for required value properties. Any type that conforms to `ImportableAttributeType` are supported. - ``` - class Animal: CoreStoreObject { - let species = Value.Required("species", initial: "") - let nickname = Value.Optional("nickname") - let color = Transformable.Optional("color") - } - ``` - - Important: `Value.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class Required: AttributeKeyPathStringConvertible, AttributeProtocol { - - /** - Initializes the metadata for the property. - ``` - class Person: CoreStoreObject { - let title = Value.Required("title", initial: "Mr.") - let name = Value.Required("name", initial: "") - let displayName = Value.Required( - "displayName", - initial: "", - isTransient: true, - customGetter: Person.getName(_:) - ) - - private static func getName(_ partialObject: PartialObject) -> String { - let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) - if !cachedDisplayName.isEmpty { - return cachedDisplayName - } - let title = partialObject.value(for: { $0.title }) - let name = partialObject.value(for: { $0.name }) - let displayName = "\(title) \(name)" - partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) - return displayName - } - } - ``` - - parameter keyPath: the permanent attribute name for this property. - - parameter initial: the initial value for the property when the object is first created - - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.primitiveValue(for:)` instead of `PartialObject.value(for:)`, which would unintentionally execute the same closure again recursively. - - parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject`, make sure to use `PartialObject.setPrimitiveValue(_:for:)` instead of `PartialObject.setValue(_:for:)`, which would unintentionally execute the same closure again recursively. - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public init( - _ keyPath: KeyPathString, - initial: @autoclosure @escaping () -> V, - isTransient: Bool = false, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - customGetter: ((_ partialObject: PartialObject) -> V)? = nil, - customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - ( - attributeType: V.cs_rawAttributeType, - isOptional: false, - isTransient: isTransient, - allowsExternalBinaryDataStorage: false, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - defaultValue: initial().cs_toQueryableNativeType() - ) - } - self.customGetter = customGetter - self.customSetter = customSetter - } - - /** - The attribute value - */ - public var value: ReturnValueType { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - if let customGetter = self.customGetter { - - return customGetter(PartialObject(object)) - } - return V.cs_fromQueryableNativeType( - object.value(forKey: self.keyPath)! as! V.QueryableNativeType - )! - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - if let customSetter = self.customSetter { - - return customSetter(PartialObject(object), newValue) - } - return object.setValue( - newValue.cs_toQueryableNativeType(), - forKey: self.keyPath - ) - } - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = V - - - // MARK: AttributeKeyPathStringConvertible - - public typealias ReturnValueType = DestinationValueType - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: AttributeProtocol - - internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in - - guard let customGetter = self.customGetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any) -> Any? in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willAccessValue(forKey: keyPath) - defer { - - rawObject.didAccessValue(forKey: keyPath) - } - let value = customGetter(PartialObject(rawObject)) - return value.cs_toQueryableNativeType() - } - } - - internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in - - guard let customSetter = self.customSetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any, _ newValue: Any?) -> Void in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willChangeValue(forKey: keyPath) - defer { - - rawObject.didChangeValue(forKey: keyPath) - } - customSetter( - PartialObject(rawObject), - V.cs_fromQueryableNativeType(newValue as! V.QueryableNativeType)! - ) - } - } - - internal var valueForSnapshot: Any? { - - return self.value - } - - - // MARK: Private - - private let customGetter: ((_ partialObject: PartialObject) -> V)? - private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? - } - - - // MARK: - Optional - - /** - The containing type for optional value properties. Any type that conforms to `ImportableAttributeType` are supported. - ``` - class Animal: CoreStoreObject { - let species = Value.Required("species", initial: "") - let nickname = Value.Optional("nickname") - let color = Transformable.Optional("color") - } - ``` - - Important: `Value.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties. - */ - public final class Optional: AttributeKeyPathStringConvertible, AttributeProtocol { - - /** - Initializes the metadata for the property. - ``` - class Person: CoreStoreObject { - let title = Value.Optional("title", initial: "Mr.") - let name = Value.Optional("name") - let displayName = Value.Optional( - "displayName", - isTransient: true, - customGetter: Person.getName(_:) - ) - - private static func getName(_ partialObject: PartialObject) -> String? { - if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) { - return cachedDisplayName - } - let title = partialObject.value(for: { $0.title }) - let name = partialObject.value(for: { $0.name }) - let displayName = "\(title) \(name)" - partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) - return displayName - } - } - ``` - - parameter keyPath: the permanent attribute name for this property. - - parameter initial: the initial value for the property when the object is first created. Defaults to `nil` if not specified. - - parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on. - - parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.) - - parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name. - - parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter. - - parameter self: the `CoreStoreObject` - - parameter getValue: the original getter for the property - - parameter customSetter: use this closure to make final transformations to the new value before assigning to the property. - - parameter setValue: the original setter for the property - - parameter finalNewValue: the transformed new value - - parameter originalNewValue: the original new value - - parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`. - */ - public init( - _ keyPath: KeyPathString, - initial: @autoclosure @escaping () -> V? = nil, - isTransient: Bool = false, - versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, - customGetter: ((_ partialObject: PartialObject) -> V?)? = nil, - customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { - - self.keyPath = keyPath - self.entityDescriptionValues = { - ( - attributeType: V.cs_rawAttributeType, - isOptional: true, - isTransient: isTransient, - allowsExternalBinaryDataStorage: false, - versionHashModifier: versionHashModifier(), - renamingIdentifier: renamingIdentifier(), - affectedByKeyPaths: affectedByKeyPaths(), - defaultValue: initial()?.cs_toQueryableNativeType() - ) - } - self.customGetter = customGetter - self.customSetter = customSetter - } - - /** - The attribute value - */ - public var value: ReturnValueType { - - get { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - if let customGetter = self.customGetter { - - return customGetter(PartialObject(object)) - } - return (object.value(forKey: self.keyPath) as! V.QueryableNativeType?) - .flatMap(V.cs_fromQueryableNativeType) - } - } - set { - - Internals.assert( - self.rawObject != nil, - "Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types." - ) - return withExtendedLifetime(self.rawObject!) { (object) in - - Internals.assert( - object.isRunningInAllowedQueue() == true, - "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." - ) - Internals.assert( - object.isEditableInContext() == true, - "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." - ) - if let customSetter = self.customSetter { - - return customSetter(PartialObject(object), newValue) - } - object.setValue( - newValue?.cs_toQueryableNativeType(), - forKey: self.keyPath - ) - } - } - } - - - // MARK: AnyKeyPathStringConvertible - - public var cs_keyPathString: String { - - return self.keyPath - } - - - // MARK: KeyPathStringConvertible - - public typealias ObjectType = O - public typealias DestinationValueType = V - - - // MARK: AttributeKeyPathStringConvertible - - public typealias ReturnValueType = DestinationValueType? - - - // MARK: PropertyProtocol - - internal let keyPath: KeyPathString - - - // MARK: AttributeProtocol - - internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues - internal var rawObject: CoreStoreManagedObject? - - internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in - - guard let customGetter = self.customGetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any) -> Any? in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willAccessValue(forKey: keyPath) - defer { - - rawObject.didAccessValue(forKey: keyPath) - } - let value = customGetter(PartialObject(rawObject)) - return value?.cs_toQueryableNativeType() - } - } - - internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in - - guard let customSetter = self.customSetter else { - - return nil - } - let keyPath = self.keyPath - return { (_ id: Any, _ newValue: Any?) -> Void in - - let rawObject = id as! CoreStoreManagedObject - rawObject.willChangeValue(forKey: keyPath) - defer { - - rawObject.didChangeValue(forKey: keyPath) - } - customSetter( - PartialObject(rawObject), - (newValue as! V.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType) - ) - } - } - - internal var valueForSnapshot: Any? { - - return self.value - } - - - // MARK: Private - - private let customGetter: ((_ partialObject: PartialObject) -> V?)? - private let customSetter: ((_ partialObject: PartialObject, _ newValue: V?) -> Void)? - } -} - - -// MARK: - Operations - -infix operator .= : AssignmentPrecedence -infix operator .== : ComparisonPrecedence - -extension ValueContainer.Required { - - /** - Assigns a value to the property. The operation - ``` - animal.species .= "Swift" - ``` - is equivalent to - ``` - animal.species.value = "Swift" - ``` - */ - public static func .= (_ property: ValueContainer.Required, _ newValue: V) { - - property.value = newValue - } - - /** - Assigns a value from another property. The operation - ``` - animal.species .= anotherAnimal.species - ``` - is equivalent to - ``` - animal.species.value = anotherAnimal.species.value - ``` - */ - public static func .= (_ property: ValueContainer.Required, _ property2: ValueContainer.Required) { - - property.value = property2.value - } - - /** - Compares equality between a property's value and another value - ``` - if animal.species .== "Swift" { ... } - ``` - is equivalent to - ``` - if animal.species.value == "Swift" { ... } - ``` - */ - public static func .== (_ property: ValueContainer.Required, _ value: V?) -> Bool { - - return property.value == value - } - - /** - Compares equality between a value and a property's value - ``` - if "Swift" .== animal.species { ... } - ``` - is equivalent to - ``` - if "Swift" == animal.species.value { ... } - ``` - */ - public static func .== (_ value: V?, _ property: ValueContainer.Required) -> Bool { - - return value == property.value - } - - /** - Compares equality between a property's value and another property's value - ``` - if animal.species .== anotherAnimal.species { ... } - ``` - is equivalent to - ``` - if animal.species.value == anotherAnimal.species.value { ... } - ``` - */ - public static func .== (_ property: ValueContainer.Required, _ property2: ValueContainer.Required) -> Bool { - - return property.value == property2.value - } - - /** - Compares equality between a property's value and another property's value - ``` - if animal.species .== anotherAnimal.species { ... } - ``` - is equivalent to - ``` - if animal.species.value == anotherAnimal.species.value { ... } - ``` - */ - public static func .== (_ property: ValueContainer.Required, _ property2: ValueContainer.Optional) -> Bool { - - return property.value == property2.value - } -} - -extension ValueContainer.Optional { - - /** - Assigns an optional value to the property. The operation - ``` - animal.nickname .= "Taylor" - ``` - is equivalent to - ``` - animal.nickname.value = "Taylor" - ``` - */ - public static func .= (_ property: ValueContainer.Optional, _ newValue: V?) { - - property.value = newValue - } - - /** - Assigns an optional value from another property. The operation - ``` - animal.nickname .= anotherAnimal.nickname - ``` - is equivalent to - ``` - animal.nickname.value = anotherAnimal.nickname.value - ``` - */ - public static func .= (_ property: ValueContainer.Optional, _ property2: ValueContainer.Optional) { - - property.value = property2.value - } - - /** - Assigns a value from another property. The operation - ``` - animal.nickname .= anotherAnimal.species - ``` - is equivalent to - ``` - animal.nickname.value = anotherAnimal.species.value - ``` - */ - public static func .= (_ property: ValueContainer.Optional, _ property2: ValueContainer.Required) { - - property.value = property2.value - } - - /** - Compares equality between a property's value and another value - ``` - if animal.species .== "Swift" { ... } - ``` - is equivalent to - ``` - if animal.species.value == "Swift" { ... } - ``` - */ - public static func .== (_ property: ValueContainer.Optional, _ value: V?) -> Bool { - - return property.value == value - } - - /** - Compares equality between a property's value and another property's value - ``` - if "Swift" .== animal.species { ... } - ``` - is equivalent to - ``` - if "Swift" == animal.species.value { ... } - ``` - */ - public static func .== (_ value: V?, _ property: ValueContainer.Optional) -> Bool { - - return value == property.value - } - - /** - Compares equality between a property's value and another property's value - ``` - if animal.species .== anotherAnimal.species { ... } - ``` - is equivalent to - ``` - if animal.species.value == anotherAnimal.species.value { ... } - ``` - */ - public static func .== (_ property: ValueContainer.Optional, _ property2: ValueContainer.Optional) -> Bool { - - return property.value == property2.value - } - - /** - Compares equality between a property's value and another property's value - ``` - if animal.species .== anotherAnimal.species { ... } - ``` - is equivalent to - ``` - if animal.species.value == anotherAnimal.species.value { ... } - ``` - */ - public static func .== (_ property: ValueContainer.Optional, _ property2: ValueContainer.Required) -> Bool { - - return property.value == property2.value - } -} +public enum ValueContainer {}