diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 1815a05..a1267e9 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -133,6 +133,42 @@ 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 */; }; + B50C3EDA23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; }; + B50C3EDB23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; }; + B50C3EDC23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; }; + B50C3EDD23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; }; + B50C3EE023D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; }; + B50C3EE123D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; }; + B50C3EE223D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; }; + B50C3EE323D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; }; + B50C3EE523D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; }; + B50C3EE623D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; }; + B50C3EE723D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; }; + B50C3EE823D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; }; + B50C3EEA23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; }; + B50C3EEB23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; }; + B50C3EEC23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; }; + B50C3EED23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; }; + B50C3EEF23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; }; + B50C3EF023D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; }; + B50C3EF123D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; }; + B50C3EF223D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; }; + B50C3EF423D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; }; + B50C3EF523D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; }; + B50C3EF623D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; }; + B50C3EF723D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; }; + B50C3EF923D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; }; + B50C3EFA23D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; }; + B50C3EFB23D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; }; + B50C3EFC23D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; }; + B50C3EFE23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; }; + B50C3EFF23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; }; + B50C3F0023D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; }; + B50C3F0123D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; }; + B50C3F0323D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; }; + B50C3F0423D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; }; + B50C3F0523D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; }; + B50C3F0623D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.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 */; }; @@ -571,10 +607,6 @@ B56E4EE523CEDF0900E1708C /* Field.Computed.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Computed.swift */; }; B56E4EE623CEDF0900E1708C /* Field.Computed.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Computed.swift */; }; B56E4EE723CEDF0900E1708C /* Field.Computed.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Computed.swift */; }; - B56E4EE923CF0C4500E1708C /* Field.PlistCoded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */; }; - B56E4EEA23CF0C4500E1708C /* Field.PlistCoded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */; }; - B56E4EEB23CF0C4500E1708C /* Field.PlistCoded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */; }; - B56E4EEC23CF0C4500E1708C /* Field.PlistCoded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */; }; B57D27BE1D0BBE8200539C58 /* BaseTestDataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */; }; B57D27BF1D0BBE8200539C58 /* BaseTestDataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */; }; B57D27C01D0BBE8200539C58 /* BaseTestDataTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */; }; @@ -967,6 +999,15 @@ 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 = ""; }; + B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldAttributeProtocol.swift; sourceTree = ""; }; + B50C3EDF23D062C300B29880 /* FieldCoderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoderType.swift; sourceTree = ""; }; + B50C3EE423D153EA00B29880 /* Field.Coded.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Coded.swift; sourceTree = ""; }; + B50C3EE923D1601400B29880 /* FieldCoders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.swift; sourceTree = ""; }; + B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.DefaultNSSecureCoding.swift; sourceTree = ""; }; + B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.NSCoding.swift; sourceTree = ""; }; + B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.Json.swift; sourceTree = ""; }; + B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.Plist.swift; sourceTree = ""; }; + B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.AnyFieldCoder.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 = ""; }; @@ -1070,7 +1111,6 @@ B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldStorableType.swift; sourceTree = ""; }; B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldOptionalType.swift; sourceTree = ""; }; B56E4EE323CEDF0900E1708C /* Field.Computed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Computed.swift; sourceTree = ""; }; - B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.PlistCoded.swift; sourceTree = ""; }; B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = ""; }; B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = ""; }; @@ -1420,6 +1460,7 @@ B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */, B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */, B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */, + B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */, B50564D22350CC3100482308 /* PropertyProtocol.swift */, B53D9E5823513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift */, B50E175623517DE4004F033C /* Differentiable.swift */, @@ -1427,6 +1468,19 @@ name = Protocols; sourceTree = ""; }; + B50C3EDE23D05BB200B29880 /* Coders */ = { + isa = PBXGroup; + children = ( + B50C3EDF23D062C300B29880 /* FieldCoderType.swift */, + B50C3EE923D1601400B29880 /* FieldCoders.swift */, + B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */, + B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */, + B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */, + B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */, + ); + name = Coders; + sourceTree = ""; + }; B51B5C2922D43854009FA3BA /* KeyPaths */ = { isa = PBXGroup; children = ( @@ -1553,7 +1607,8 @@ B56E4EC923CD9B4800E1708C /* Field.swift */, B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */, B56E4EE323CEDF0900E1708C /* Field.Computed.swift */, - B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */, + B50C3EE423D153EA00B29880 /* Field.Coded.swift */, + B50C3EDE23D05BB200B29880 /* Coders */, B56E4EDD23CEBB0400E1708C /* Supported Values */, ); name = "Field Properties"; @@ -1846,6 +1901,7 @@ B50564CC2350699700482308 /* Protocols */, B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */, B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */, + B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */, B5C976E61C6E3A5900B1AF90 /* Internals.CoreStoreFetchedResultsController.swift */, B5474D142227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift */, B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */, @@ -2206,6 +2262,7 @@ B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, B5CA2B081F7E5ACA004B1936 /* WhereClauseType.swift in Sources */, B50E17612351FA66004F033C /* Internals.Closure.swift in Sources */, + B50C3EEA23D1601400B29880 /* FieldCoders.swift in Sources */, B5C976E71C6E3A5A00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */, B56923F51EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */, B5F1DA901B9AA991007C5CBB /* ImportableUniqueObject.swift in Sources */, @@ -2229,6 +2286,7 @@ B55BB4DB23503B9700C33E34 /* EnvironmentValues+DataSources.swift in Sources */, B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */, B54A6A551BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift in Sources */, + B50C3F0323D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */, B5D339E21E948C3600C880DE /* Value.swift in Sources */, B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */, B53FBA0B1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, @@ -2250,7 +2308,6 @@ B5E1B5A81CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */, B50132302346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */, B5D339F11E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, - B56E4EE923CF0C4500E1708C /* Field.PlistCoded.swift in Sources */, B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */, B5E1B59D1CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */, B5ECDC231CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */, @@ -2270,6 +2327,7 @@ B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */, B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */, B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */, + B50C3EFE23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */, B56E4EDF23CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */, B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, @@ -2306,6 +2364,7 @@ B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5FAD6AC1B51285300714891 /* Internals.MigrationManager.swift in Sources */, B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, + B50C3EE023D062C300B29880 /* FieldCoderType.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */, B5BF7FCB234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */, @@ -2320,6 +2379,7 @@ B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */, B5D339DD1E9489C700C880DE /* DynamicObject.swift in Sources */, B5A5F2661CAEC50F004AB9AF /* CSSelect.swift in Sources */, + B50C3EE523D153EA00B29880 /* Field.Coded.swift in Sources */, B5ECDBE51CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */, B5519A4A1CA1F4FB002BEF78 /* CSError.swift in Sources */, B52F742F1E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, @@ -2333,6 +2393,7 @@ B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, B5E222231CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */, + B50C3EF423D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */, B56E4ECF23CD9E4200E1708C /* Field.Stored.swift in Sources */, B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, @@ -2345,7 +2406,9 @@ B5D33A011E96012400C880DE /* Relationship.swift in Sources */, B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, + B50C3EF923D1987D00B29880 /* FieldCoders.Json.swift in Sources */, B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */, + B50C3EDA23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */, B56923E41EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */, B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DB1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, @@ -2385,6 +2448,7 @@ B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, + B50C3EEF23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B5277672234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, B5DBE2CD1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, @@ -2446,6 +2510,7 @@ B56923F61EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */, 82BA18A21C4BBD1D00A0916E /* CoreStoreError.swift in Sources */, B512608A1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, + B50C3EEB23D1601400B29880 /* FieldCoders.swift in Sources */, 82BA18B21C4BBD3900A0916E /* ImportableObject.swift in Sources */, 82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */, 82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */, @@ -2469,6 +2534,7 @@ B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */, B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, B5D339E31E948C3600C880DE /* Value.swift in Sources */, + B50C3F0423D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */, B50E175323517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */, 82BA18C51C4BBD5300A0916E /* ListObserver.swift in Sources */, B53FBA0D1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, @@ -2490,7 +2556,6 @@ B5ECDC251CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */, B549F6741E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */, - B56E4EEA23CF0C4500E1708C /* Field.PlistCoded.swift in Sources */, 82BA18B31C4BBD3900A0916E /* ImportableUniqueObject.swift in Sources */, B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, B55BB4DA23503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */, @@ -2510,6 +2575,7 @@ B5831F432212700400D8604C /* Where.Expression.swift in Sources */, B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, B5FE4DA81C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, + B50C3EFF23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */, B56E4EE023CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B5F8496D234898240029D57B /* ListSnapshot.swift in Sources */, B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, @@ -2546,6 +2612,7 @@ B596BBB71DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B5FEC18F1C9166E600532541 /* NSPersistentStore+Setup.swift in Sources */, 82BA18B71C4BBD3F00A0916E /* CoreStore+Querying.swift in Sources */, + B50C3EE123D062C300B29880 /* FieldCoderType.swift in Sources */, 82BA18AA1C4BBD3100A0916E /* BaseDataTransaction.swift in Sources */, 82BA18A91C4BBD3100A0916E /* Into.swift in Sources */, 82BA18D11C4BBD7100A0916E /* Internals.NotificationObserver.swift in Sources */, @@ -2560,6 +2627,7 @@ B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */, 82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */, + B50C3EE623D153EA00B29880 /* Field.Coded.swift in Sources */, 82BA18C71C4BBD5900A0916E /* CoreStore+Migration.swift in Sources */, B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, 82BA18C41C4BBD5300A0916E /* ListMonitor.swift in Sources */, @@ -2573,6 +2641,7 @@ B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, 82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */, B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, + B50C3EF523D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */, 82BA18D81C4BBD7100A0916E /* Internals.WeakObject.swift in Sources */, B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, @@ -2585,7 +2654,9 @@ B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DC1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5ECDC311CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, + B50C3EFA23D1987D00B29880 /* FieldCoders.Json.swift in Sources */, B5DE522C230BD7D500A22534 /* Internals.swift in Sources */, + B50C3EDB23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */, 82BA18AF1C4BBD3100A0916E /* CoreStore+Transaction.swift in Sources */, 82BA18CB1C4BBD6400A0916E /* NSManagedObject+Convenience.swift in Sources */, 82BA18B51C4BBD3F00A0916E /* BaseDataTransaction+Querying.swift in Sources */, @@ -2625,6 +2696,7 @@ B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, + B50C3EF023D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, B5ECDC0D1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5D339E81E9493A500C880DE /* Entity.swift in Sources */, B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */, @@ -2686,6 +2758,7 @@ B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5CA2B0B1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */, B56923F81EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */, + B50C3EED23D1601400B29880 /* FieldCoders.swift in Sources */, B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */, B512608C1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, B5ECDC151CA816E500C7F112 /* CSTweak.swift in Sources */, @@ -2709,6 +2782,7 @@ B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74441E9B8724005F3DAC /* UnsafeDataModelSchema.swift in Sources */, B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, + B50C3F0623D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */, B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */, B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */, B5D339E51E948C3600C880DE /* Value.swift in Sources */, @@ -2730,7 +2804,6 @@ B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */, B50E17642351FA66004F033C /* Internals.Closure.swift in Sources */, B5ECDBF01CA6BF2000C7F112 /* CSFrom.swift in Sources */, - B56E4EEC23CF0C4500E1708C /* Field.PlistCoded.swift in Sources */, B549F6761E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */, B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */, @@ -2750,6 +2823,7 @@ B52DD1BD1BE1F94300949AFE /* NSManagedObject+Convenience.swift in Sources */, B5831F4222126FED00D8604C /* KeyPathGenericBindings.swift in Sources */, B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */, + B50C3F0123D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */, B56E4EE223CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */, B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, @@ -2786,6 +2860,7 @@ B52F74481E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52DD19C1BE1F92C00949AFE /* Into.swift in Sources */, + B50C3EE323D062C300B29880 /* FieldCoderType.swift in Sources */, B5FE4DA51C8481E100FA6A91 /* StorageInterface.swift in Sources */, B596BBB91DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B5FE4DAA1C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, @@ -2800,6 +2875,7 @@ B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */, B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */, B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */, + B50C3EE823D153EA00B29880 /* Field.Coded.swift in Sources */, B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */, B52DD1CB1BE1F94600949AFE /* Internals.WeakObject.swift in Sources */, @@ -2813,6 +2889,7 @@ B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */, B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */, B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, + B50C3EF723D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */, B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */, @@ -2825,7 +2902,9 @@ B58D0C661EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DE1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, B5220E141D130614009BC71E /* DataStack+Observing.swift in Sources */, + B50C3EFC23D1987D00B29880 /* FieldCoders.Json.swift in Sources */, B52DD1A21BE1F92C00949AFE /* CoreStore+Transaction.swift in Sources */, + B50C3EDD23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */, B5DE522E230BD7D600A22534 /* Internals.swift in Sources */, B5E2222E1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, B5220E191D130761009BC71E /* ListMonitor.swift in Sources */, @@ -2865,6 +2944,7 @@ B5519A5C1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */, B5DBE2D51C991B3E00B5CEFA /* CSDataStack.swift in Sources */, B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, + B50C3EF223D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, B5AEFAB81C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B598514B1C90289F00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5D339EA1E9493A500C880DE /* Entity.swift in Sources */, @@ -2926,6 +3006,7 @@ B56923F71EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */, B56321801BD65216006C9394 /* CoreStoreError.swift in Sources */, B512608B1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */, + B50C3EEC23D1601400B29880 /* FieldCoders.swift in Sources */, B56321AD1BD6521C006C9394 /* Internals.MigrationManager.swift in Sources */, B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */, B56321961BD65216006C9394 /* From.swift in Sources */, @@ -2949,6 +3030,7 @@ B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */, B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */, B5D339E41E948C3600C880DE /* Value.swift in Sources */, + B50C3F0523D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */, B50E175423517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */, B53FBA0E1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */, B563219E1BD65216006C9394 /* CoreStore+Observing.swift in Sources */, @@ -2970,7 +3052,6 @@ B563217F1BD65216006C9394 /* CoreStore.swift in Sources */, B549F6751E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */, B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */, - B56E4EEB23CF0C4500E1708C /* Field.PlistCoded.swift in Sources */, B5E1B5961CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */, B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, B55BB4D923503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */, @@ -2990,6 +3071,7 @@ B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B5FE4DA91C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B5831F442212700500D8604C /* Where.Expression.swift in Sources */, + B50C3F0023D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */, B56E4EE123CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B5F8496E234898240029D57B /* ListSnapshot.swift in Sources */, B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, @@ -3026,6 +3108,7 @@ B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */, B5E1B5A51CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, B596BBB81DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, + B50C3EE223D062C300B29880 /* FieldCoderType.swift in Sources */, B56321881BD65216006C9394 /* BaseDataTransaction.swift in Sources */, B56321A31BD65216006C9394 /* DataStack+Migration.swift in Sources */, B56321901BD65216006C9394 /* ImportableUniqueObject.swift in Sources */, @@ -3040,6 +3123,7 @@ B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */, B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */, B56321991BD65216006C9394 /* OrderBy.swift in Sources */, + B50C3EE723D153EA00B29880 /* Field.Coded.swift in Sources */, B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */, B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */, B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, @@ -3053,6 +3137,7 @@ B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, + B50C3EF623D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */, B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */, B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */, B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */, @@ -3065,7 +3150,9 @@ B56923E61EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */, B58D0C651EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */, B533C4DD1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */, + B50C3EFB23D1987D00B29880 /* FieldCoders.Json.swift in Sources */, B5ECDC321CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, + B50C3EDC23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */, B5DE522D230BD7D600A22534 /* Internals.swift in Sources */, B56321851BD65216006C9394 /* CoreStore+Logging.swift in Sources */, B56321921BD65216006C9394 /* BaseDataTransaction+Querying.swift in Sources */, @@ -3105,6 +3192,7 @@ B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, + B50C3EF123D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */, B5ECDC0E1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5D339E91E9493A500C880DE /* Entity.swift in Sources */, B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */, diff --git a/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents b/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents index 7df0666..f334ebd 100644 --- a/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents +++ b/CoreStoreDemo/CoreStoreDemo/CoreStoreDemo.xcdatamodeld/CoreStoreDemo.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -20,7 +20,7 @@ - + \ No newline at end of file diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index 99f4e3a..7cd1362 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -41,8 +41,10 @@ class Animal: CoreStoreObject { @Field.Stored("species") var species: String = "Swift" + @Field.Coded("color", coder: FieldCoders.NSCoding.self) + var color: Color? = .blue + let master = Relationship.ToOne("master") - let color = Transformable.Optional("color") } class Dog: Animal { @@ -55,6 +57,10 @@ class Dog: Animal { let friendedBy = Relationship.ToManyUnordered("friendedBy", inverse: { $0.friends }) } +struct CustomType { + var string = "customString" +} + class Person: CoreStoreObject { @Field.Stored("title", customSetter: Person.setTitle(_:_:)) @@ -70,12 +76,50 @@ class Person: CoreStoreObject { ) var displayName: String? + @Field.Computed( + "customType", + customGetter: Person.getCustomField(_:) + ) + var customField: CustomType + + @Field.Coded( + "job", coder: ( + encode: { $0.data }, + decode: { $0.flatMap(Job.init(data:)) ?? .unemployed } + ) + ) + var job: Job = .unemployed + let spouse = Relationship.ToOne("spouse") let pets = Relationship.ToManyUnordered("pets", inverse: { $0.master }) private let _spouse = Relationship.ToOne("_spouseInverse", inverse: { $0.spouse }) + enum Job: String { + + case unemployed + case engineer + case doctor + case lawyer + + init?(data: Data) { + + guard + let rawValue = String(data: data, encoding: .utf8), + let value = Self.init(rawValue: rawValue) + else { + + return nil + } + self = value + } + + var data: Data { + + return Data(self.rawValue.utf8) + } + } private static func setTitle(_ partialObject: PartialObject, _ newValue: String) { @@ -88,6 +132,17 @@ class Person: CoreStoreObject { partialObject.setPrimitiveValue(newValue, for: \.$name) partialObject.setPrimitiveValue(nil, for: \.$displayName) } + + static func getCustomField(_ partialObject: PartialObject) -> CustomType { + + if let customField = partialObject.primitiveValue(for: \.$customField) { + + return customField + } + let customField = CustomType() + partialObject.setPrimitiveValue(customField, for: \.$customField) + return customField + } static func getDisplayName(_ partialObject: PartialObject) -> String? { @@ -130,7 +185,7 @@ class DynamicModelTests: BaseTestDataTestCase { versionLock: [ "Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a], "Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7], - "Person": [0x2831cf046084d96d, 0xbe19b13ace54641, 0x635a082728b0f6f0, 0x3d4ef2dd4b74a87c] + "Person": [0xf3e6ba6016bbedc6, 0x50dedf64f0eba490, 0xa32088a0ee83468d, 0xb72d1d0b37bd0992] ] ) ) @@ -156,12 +211,13 @@ class DynamicModelTests: BaseTestDataTestCase { let animal = transaction.create(Into()) XCTAssertEqual(animal.species, "Swift") XCTAssertTrue(type(of: animal.species) == String.self) + XCTAssertEqual(animal.color, Color.blue) animal.species = "Sparrow" XCTAssertEqual(animal.species, "Sparrow") - animal.color .= .yellow - XCTAssertEqual(animal.color.value, Color.yellow) + animal.color = .yellow + XCTAssertEqual(animal.color, Color.yellow) for property in Animal.metaProperties(includeSuperclasses: true) { @@ -173,8 +229,8 @@ class DynamicModelTests: BaseTestDataTestCase { case String(keyPath: \Animal.master): XCTAssertTrue(property is RelationshipContainer.ToOne) - case String(keyPath: \Animal.color): - XCTAssertTrue(property is TransformableContainer.Optional) + case String(keyPath: \Animal.$color): + XCTAssertTrue(property is FieldContainer.Coded) default: XCTFail("Unknown KeyPath: \"\(property.keyPath)\"") @@ -196,8 +252,8 @@ class DynamicModelTests: BaseTestDataTestCase { case String(keyPath: \Dog.master): XCTAssertTrue(property is RelationshipContainer.ToOne) - case String(keyPath: \Dog.color): - XCTAssertTrue(property is TransformableContainer.Optional) + case String(keyPath: \Dog.$color): + XCTAssertTrue(property is FieldContainer.Coded) case String(keyPath: \Dog.$nickname): XCTAssertTrue(property is FieldContainer.Stored) @@ -270,6 +326,8 @@ class DynamicModelTests: BaseTestDataTestCase { let person = transaction.create(Into()) XCTAssertTrue(person.pets.value.isEmpty) + XCTAssertEqual(person.customField.string, "customString") + XCTAssertEqual(person.job, .unemployed) XCTAssertEqual( person.rawObject! @@ -301,6 +359,12 @@ class DynamicModelTests: BaseTestDataTestCase { XCTAssertEqual(personSnapshot1.$name, "John") XCTAssertEqual(personSnapshot1.$title, "Mr.") XCTAssertEqual(personSnapshot1.$displayName, "Mr. John") + + person.customField.string = "newCustomString" + XCTAssertEqual(person.customField.string, "newCustomString") + + person.job = .engineer + XCTAssertEqual(person.job, .engineer) let personSnapshot2 = person.asSnapshot(in: transaction)! XCTAssertEqual(person.name, personSnapshot2.$name) @@ -315,6 +379,7 @@ class DynamicModelTests: BaseTestDataTestCase { XCTAssertEqual(personSnapshot2.$displayName, "Sir John") XCTAssertEqual(personSnapshot3.$name, "James") XCTAssertEqual(personSnapshot3.$displayName, "Sir John") + person.pets.value.insert(dog) @@ -342,6 +407,7 @@ class DynamicModelTests: BaseTestDataTestCase { let bird = try transaction.fetchOne(From(), p1) XCTAssertNotNil(bird) XCTAssertEqual(bird!.species, "Sparrow") + XCTAssertEqual(bird!.color, Color.yellow) let p2 = Where({ $0.$nickname == "Spot" }) XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot")) @@ -353,6 +419,11 @@ class DynamicModelTests: BaseTestDataTestCase { let person = try transaction.fetchOne(From()) XCTAssertNotNil(person) + XCTAssertEqual(person!.name, "John") + XCTAssertEqual(person!.title, "Sir") + XCTAssertEqual(person!.displayName, "Sir John") + XCTAssertEqual(person!.customField.string, "customString") + XCTAssertEqual(person!.job, .engineer) XCTAssertEqual(person!.pets.value.first, dog) let p3 = Where({ $0.age == 10 }) diff --git a/CoreStoreTests/WhereTests.swift b/CoreStoreTests/WhereTests.swift index 9ceec1f..1dbff23 100644 --- a/CoreStoreTests/WhereTests.swift +++ b/CoreStoreTests/WhereTests.swift @@ -67,7 +67,7 @@ final class WhereTests: XCTestCase { dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() { XCTAssertAllEqual(String(keyPath: \TestEntity1.testEntityID), "testEntityID") - XCTAssertAllEqual(String(keyPath: \Animal.color), "color") + XCTAssertAllEqual(String(keyPath: \Animal.$color), "color") } @objc diff --git a/Sources/CoreStoreSchema.swift b/Sources/CoreStoreSchema.swift index 39252ad..ef6f2c7 100644 --- a/Sources/CoreStoreSchema.swift +++ b/Sources/CoreStoreSchema.swift @@ -209,20 +209,23 @@ public final class CoreStoreSchema: DynamicSchema { let rawModel = NSManagedObjectModel() var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] var allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:] + var allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:] for entity in self.allEntities { - let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription( + let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = self.entityDescription( for: entity, initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:) ) entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription) allCustomGettersSetters[entity] = customGetterSetterByKeyPaths + allFieldCoders[entity] = fieldCoders } CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity) CoreStoreSchema.thirdPassConnectInheritanceTreeAndIndexes(for: entityDescriptionsByEntity) CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses( for: entityDescriptionsByEntity, - allCustomGettersSetters: allCustomGettersSetters + allCustomGettersSetters: allCustomGettersSetters, + allFieldCoders: allFieldCoders ) rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! }) @@ -254,22 +257,46 @@ public final class CoreStoreSchema: DynamicSchema { private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:] private var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:] + private var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:] private weak var cachedRawModel: NSManagedObjectModel? - private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]) { + private func entityDescription( + for entity: DynamicEntity, + initializer: (DynamicEntity, ModelVersion) -> ( + entity: NSEntityDescription, + customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter], + fieldCoders: [KeyPathString: Internals.AnyFieldCoder] + ) + ) -> ( + entity: NSEntityDescription, + customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter], + fieldCoders: [KeyPathString: Internals.AnyFieldCoder] + ) { if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] { - return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:]) + return ( + cachedEntityDescription, + self.customGettersSettersByEntity[entity] ?? [:], + self.fieldCodersByEntity[entity] ?? [:] + ) } let modelVersion = self.modelVersion - let (entityDescription, customGetterSetterByKeyPaths) = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) }) + let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = withoutActuallyEscaping( + initializer, + do: { $0(entity, modelVersion) } + ) self.entityDescriptionsByEntity[entity] = entityDescription self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths - return (entityDescription, customGetterSetterByKeyPaths) + self.fieldCodersByEntity[entity] = fieldCoders + return (entityDescription, customGetterSetterByKeyPaths, fieldCoders) } - - private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]) { + + private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> ( + entity: NSEntityDescription, + customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter], + fieldCoders: [KeyPathString: Internals.AnyFieldCoder] + ) { let entityDescription = NSEntityDescription() entityDescription.coreStoreEntity = entity @@ -280,6 +307,7 @@ public final class CoreStoreSchema: DynamicSchema { var keyPathsByAffectedKeyPaths: [KeyPathString: Set] = [:] var customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter] = [:] + var fieldCoders: [KeyPathString: Internals.AnyFieldCoder] = [:] func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] { var propertyDescriptions: [NSPropertyDescription] = [] @@ -302,9 +330,15 @@ public final class CoreStoreSchema: DynamicSchema { description.allowsExternalBinaryDataStorage = entityDescriptionValues.allowsExternalBinaryDataStorage description.versionHashModifier = entityDescriptionValues.versionHashModifier description.renamingIdentifier = entityDescriptionValues.renamingIdentifier + + let valueTransformer = entityDescriptionValues.valueTransformer + description.valueTransformerName = valueTransformer?.transformerName.rawValue + propertyDescriptions.append(description) + keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter) + fieldCoders[attribute.keyPath] = valueTransformer case let attribute as AttributeProtocol: Internals.assert( @@ -350,7 +384,7 @@ public final class CoreStoreSchema: DynamicSchema { } entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type) entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths - return (entityDescription, customGetterSetterByKeyPaths) + return (entityDescription, customGetterSetterByKeyPaths, fieldCoders) } private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) { @@ -523,7 +557,11 @@ public final class CoreStoreSchema: DynamicSchema { } } - private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]]) { + private static func fourthPassSynthesizeManagedObjectClasses( + for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], + allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]], + allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] + ) { func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]?) { @@ -636,5 +674,13 @@ public final class CoreStoreSchema: DynamicSchema { customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity] ) } + + _ = allFieldCoders + .flatMap({ (_, values) in values }) + .reduce( + into: [:] as [NSValueTransformerName: Internals.AnyFieldCoder], + { (result, element) in result[element.value.transformerName] = element.value } + ) + .forEach({ (_, fieldCoder) in fieldCoder.register() }) } } diff --git a/Sources/DynamicSchema+Convenience.swift b/Sources/DynamicSchema+Convenience.swift index f357116..c70ccc4 100644 --- a/Sources/DynamicSchema+Convenience.swift +++ b/Sources/DynamicSchema+Convenience.swift @@ -177,6 +177,9 @@ extension DynamicSchema { defaultString = ", initial: /* required */" } + case .undefinedAttributeType: + #warning("TODO: Field.Computed") + continue default: fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)") } diff --git a/Sources/Field.Coded.swift b/Sources/Field.Coded.swift new file mode 100644 index 0000000..94e30fd --- /dev/null +++ b/Sources/Field.Coded.swift @@ -0,0 +1,532 @@ +// +// Field.Coded.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: - FieldContainer + +extension FieldContainer { + + // MARK: - Coded + + /** + The containing type for stored property values. Any type that conforms to `FieldStorableType` are supported. + ``` + class Animal: CoreStoreObject { + @Field.Stored("species") + var species = "" + + @Field.Computed("pluralName", customGetter: Animal.pluralName(_:)) + var pluralName: String = "" + + @Field.PlistCoded("color") + var color: UIColor? + } + ``` + - Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Stored(...) var` syntax will be ignored. + */ + @propertyWrapper + public struct Coded: AttributeKeyPathStringConvertible, FieldAttributeProtocol { + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + @Field.Stored("title") + var title: String = "Mr." + + @Field.Stored("name") + var name: String = "" + + @Field.Computed("displayName", customGetter: Person.getName(_:)) + var displayName: String = "" + + private static func getName(_ partialObject: PartialObject) -> String { + let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName) + if !cachedDisplayName.isEmpty { + return cachedDisplayName + } + let title = partialObject.value(for: \.$title) + let name = partialObject.value(for: \.$name) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } + } + ``` + - parameter initial: the initial value for the property when the object is first create + - parameter keyPath: the permanent attribute name for this property. + - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. + - 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( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder fieldCoderType: Coder.Type, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where Coder.FieldStoredValue == V { + + self.init( + defaultValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(fieldCoderType) }, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: (encode: (V) -> Data?, decode: (Data?) -> V), + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + + // MARK: @propertyWrapper + + @available(*, unavailable) + public var wrappedValue: V { + + get { fatalError() } + set { fatalError() } + } + + public var projectedValue: Self { + + return self + } + + public static subscript( + _enclosingInstance instance: O, + wrapped wrappedKeyPath: ReferenceWritableKeyPath, + storage storageKeyPath: ReferenceWritableKeyPath + ) -> V { + + get { + + Internals.assert( + instance.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 self.read(field: instance[keyPath: storageKeyPath], for: instance.rawObject!) as! V + } + set { + + Internals.assert( + instance.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 self.modify(field: instance[keyPath: storageKeyPath], for: instance.rawObject!, newValue: newValue) + } + } + + + // 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: FieldProtocol + + internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? { + + Internals.assert( + rawObject.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + let field = field as! Self + if let customGetter = field.customGetter { + + return customGetter(PartialObject(rawObject)) + } + let keyPath = field.keyPath + switch rawObject.value(forKey: keyPath) { + + case let rawValue as V: + return rawValue + + default: + return nil + } + } + + internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) { + + Internals.assert( + rawObject.isRunningInAllowedQueue() == true, + "Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue." + ) + Internals.assert( + rawObject.isEditableInContext() == true, + "Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction." + ) + let newValue = newValue as! V + let field = field as! Self + let keyPath = field.keyPath + if let customSetter = field.customSetter { + + return customSetter(PartialObject(rawObject), newValue) + } + return rawObject.setValue(newValue, forKey: keyPath) + } + + + // MARK: FieldAttributeProtocol + + internal let entityDescriptionValues: () -> FieldAttributeProtocol.EntityDescriptionValues + + internal var getter: CoreStoreManagedObject.CustomGetter? { + + let keyPath = self.keyPath + guard let customGetter = self.customGetter else { + + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + switch rawObject.primitiveValue(forKey: keyPath) { + + case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox: + rawObject.setPrimitiveValue(valueBox.value, forKey: keyPath) + return valueBox.value + + case let value: + return value + } + } + } + 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 var setter: CoreStoreManagedObject.CustomSetter? { + + 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 + ) + } + } + + + // MARK: FilePrivate + + fileprivate init( + defaultValue: @escaping () -> Any?, + keyPath: KeyPathString, + isOptional: Bool, + versionHashModifier: @escaping () -> String?, + renamingIdentifier: @escaping () -> String?, + valueTransformer: @escaping () -> Internals.AnyFieldCoder?, + customGetter: ((_ partialObject: PartialObject) -> V)?, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? , + affectedByKeyPaths: @escaping () -> Set) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + + let fieldCoder = valueTransformer() + return ( + attributeType: .transformableAttributeType, + isOptional: isOptional, + isTransient: false, + allowsExternalBinaryDataStorage: false, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + valueTransformer: fieldCoder, + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: Internals.AnyFieldCoder.TransformableDefaultValueCodingBox( + defaultValue: defaultValue(), + fieldCoder: fieldCoder + ) + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? + } +} + + +// MARK: - FieldContainer.Coded where V: FieldOptionalType + +extension FieldContainer.Coded where V: FieldOptionalType { + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + @Field.Stored("title") + var title: String = "Mr." + + @Field.Stored("name") + var name: String = "" + + @Field.Computed("displayName", customGetter: Person.getName(_:)) + var displayName: String = "" + + private static func getName(_ partialObject: PartialObject) -> String { + let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName) + if !cachedDisplayName.isEmpty { + return cachedDisplayName + } + let title = partialObject.value(for: \.$title) + let name = partialObject.value(for: \.$name) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } + } + ``` + - parameter initial: the initial value for the property when the object is first create + - parameter keyPath: the permanent attribute name for this property. + - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. + - 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( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: Coder.Type, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) where Coder.FieldStoredValue == V.Wrapped { + + self.init( + defaultValue: { initial().cs_wrappedValue }, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(coder) }, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + public init( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + coder: (encode: (V) -> Data?, decode: (Data?) -> V), + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: { initial().cs_wrappedValue }, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) }, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } +} + + +// MARK: - FieldContainer.Coded where V: DefaultNSSecureCodable + +extension FieldContainer.Coded where V: DefaultNSSecureCodable { + + /** + Initializes the metadata for the property. + ``` + class Person: CoreStoreObject { + @Field.Stored("title") + var title: String = "Mr." + + @Field.Stored("name") + var name: String = "" + + @Field.Computed("displayName", customGetter: Person.getName(_:)) + var displayName: String = "" + + private static func getName(_ partialObject: PartialObject) -> String { + let cachedDisplayName = partialObject.primitiveValue(for: \.$displayName) + if !cachedDisplayName.isEmpty { + return cachedDisplayName + } + let title = partialObject.value(for: \.$title) + let name = partialObject.value(for: \.$name) + let displayName = "\(title) \(name)" + partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + return displayName + } + } + ``` + - parameter initial: the initial value for the property when the object is first create + - parameter keyPath: the permanent attribute name for this property. + - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. + - 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( + wrappedValue initial: @autoclosure @escaping () -> V, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } +} + + +// MARK: - FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable + +extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable { + + public init( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ keyPath: KeyPathString, + versionHashModifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, + customGetter: ((_ partialObject: PartialObject) -> V)? = nil, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { + + self.init( + defaultValue: { initial().cs_wrappedValue }, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: previousVersionKeyPath, + valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding.self) }, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } +} diff --git a/Sources/Field.Computed.swift b/Sources/Field.Computed.swift index 1bd55dd..8693032 100644 --- a/Sources/Field.Computed.swift +++ b/Sources/Field.Computed.swift @@ -79,14 +79,12 @@ extension FieldContainer { } ``` - parameter keyPath: the permanent attribute name for this property. - - 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, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, customGetter: ((_ partialObject: PartialObject) -> V)? = nil, customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { @@ -94,7 +92,6 @@ extension FieldContainer { self.init( keyPath: keyPath, isOptional: false, - renamingIdentifier: renamingIdentifier, customGetter: customGetter, customSetter: customSetter, affectedByKeyPaths: affectedByKeyPaths @@ -102,14 +99,13 @@ extension FieldContainer { } /** - Overload for compiler message only + Overload for compiler error message only */ - @available(*, unavailable, message: "Field.Computed properties are not allowed to have default values.") + @available(*, unavailable, message: "Field.Computed properties are not allowed to have initial values, including `nil`.") public init( wrappedValue initial: @autoclosure @escaping () -> V, _ keyPath: KeyPathString, 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 = []) { @@ -274,7 +270,6 @@ extension FieldContainer { fileprivate init( keyPath: KeyPathString, isOptional: Bool, - renamingIdentifier: @escaping () -> String?, customGetter: ((_ partialObject: PartialObject) -> V)?, customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? , affectedByKeyPaths: @escaping () -> Set) { @@ -287,7 +282,8 @@ extension FieldContainer { isTransient: true, allowsExternalBinaryDataStorage: false, versionHashModifier: nil, - renamingIdentifier: renamingIdentifier(), + renamingIdentifier: nil, + valueTransformer: nil, affectedByKeyPaths: affectedByKeyPaths(), defaultValue: nil ) diff --git a/Sources/Field.PlistCoded.swift b/Sources/Field.PlistCoded.swift deleted file mode 100644 index 192ae0d..0000000 --- a/Sources/Field.PlistCoded.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Field.PlistCoded.swift -// CoreStore -// -// Created by John Estropia on 2020/01/15. -// Copyright © 2020 John Rommel Estropia. All rights reserved. -// - -import Foundation diff --git a/Sources/Field.Stored.swift b/Sources/Field.Stored.swift index d9a7b0a..16babcd 100644 --- a/Sources/Field.Stored.swift +++ b/Sources/Field.Stored.swift @@ -81,7 +81,7 @@ extension FieldContainer { - parameter initial: the initial value for the property when the object is first create - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - 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:)`. @@ -90,17 +90,18 @@ extension FieldContainer { wrappedValue initial: @autoclosure @escaping () -> V, _ keyPath: KeyPathString, versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, customGetter: ((_ partialObject: PartialObject) -> V)? = nil, customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, - affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { + affectedByKeyPaths: @autoclosure @escaping () -> Set = [] + ) { self.init( wrappedValue: initial, keyPath: keyPath, isOptional: false, versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, + renamingIdentifier: previousVersionKeyPath, customGetter: customGetter, customSetter: customSetter, affectedByKeyPaths: affectedByKeyPaths @@ -185,11 +186,14 @@ extension FieldContainer { return customGetter(PartialObject(rawObject)) } let keyPath = field.keyPath - guard case let rawValue as V.FieldStoredNativeType = rawObject.value(forKey: keyPath) else { + switch rawObject.value(forKey: keyPath) { + case let rawValue as V.FieldStoredNativeType: + return V.cs_fromFieldStoredNativeType(rawValue) + + default: return nil } - return V.cs_fromFieldStoredNativeType(rawValue) } internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) { @@ -284,6 +288,7 @@ extension FieldContainer { allowsExternalBinaryDataStorage: false, versionHashModifier: versionHashModifier(), renamingIdentifier: renamingIdentifier(), + valueTransformer: nil, affectedByKeyPaths: affectedByKeyPaths(), defaultValue: initial().cs_toFieldStoredNativeType() ) @@ -334,7 +339,7 @@ extension FieldContainer.Stored where V: FieldOptionalType { - parameter initial: the initial value for the property when the object is first create - parameter keyPath: the permanent attribute name for this property. - 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 previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` 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 `keyPath`. - 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:)`. @@ -343,7 +348,7 @@ extension FieldContainer.Stored where V: FieldOptionalType { wrappedValue initial: @autoclosure @escaping () -> V = nil, _ keyPath: KeyPathString, versionHashModifier: @autoclosure @escaping () -> String? = nil, - renamingIdentifier: @autoclosure @escaping () -> String? = nil, + previousVersionKeyPath: @autoclosure @escaping () -> String? = nil, customGetter: ((_ partialObject: PartialObject) -> V)? = nil, customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? = nil, affectedByKeyPaths: @autoclosure @escaping () -> Set = []) { @@ -353,7 +358,7 @@ extension FieldContainer.Stored where V: FieldOptionalType { keyPath: keyPath, isOptional: true, versionHashModifier: versionHashModifier, - renamingIdentifier: renamingIdentifier, + renamingIdentifier: previousVersionKeyPath, customGetter: customGetter, customSetter: customSetter, affectedByKeyPaths: affectedByKeyPaths diff --git a/Sources/FieldAttributeProtocol.swift b/Sources/FieldAttributeProtocol.swift new file mode 100644 index 0000000..367434f --- /dev/null +++ b/Sources/FieldAttributeProtocol.swift @@ -0,0 +1,49 @@ +// +// FieldAttributeProtocol.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 Foundation +import CoreData + + +// MARK: - FieldAttributeProtocol + +internal protocol FieldAttributeProtocol: FieldProtocol { + + typealias EntityDescriptionValues = ( + attributeType: NSAttributeType, + isOptional: Bool, + isTransient: Bool, + allowsExternalBinaryDataStorage: Bool, + versionHashModifier: String?, + renamingIdentifier: String?, + valueTransformer: Internals.AnyFieldCoder?, + affectedByKeyPaths: Set, + defaultValue: Any? + ) + + var entityDescriptionValues: () -> EntityDescriptionValues { get } + var getter: CoreStoreManagedObject.CustomGetter? { get } + var setter: CoreStoreManagedObject.CustomSetter? { get } +} diff --git a/Sources/FieldCoderType.swift b/Sources/FieldCoderType.swift new file mode 100644 index 0000000..1189017 --- /dev/null +++ b/Sources/FieldCoderType.swift @@ -0,0 +1,38 @@ +// +// FieldCoderType.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 Foundation +import CoreData + + +// MARK: - FieldCoderType + +public protocol FieldCoderType { + + associatedtype FieldStoredValue + + static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? + static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? +} diff --git a/Sources/FieldCoders.DefaultNSSecureCoding.swift b/Sources/FieldCoders.DefaultNSSecureCoding.swift new file mode 100644 index 0000000..989c4ec --- /dev/null +++ b/Sources/FieldCoders.DefaultNSSecureCoding.swift @@ -0,0 +1,87 @@ +// +// FieldCoders.DefaultNSSecureCoding.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 Foundation + + +// MARK: - FieldCoders + +extension FieldCoders { + + public struct DefaultNSSecureCoding: FieldCoderType { + + // MARK: FieldCoderType + + public typealias FieldStoredValue = T + + public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? { + + return ValueTransformer(forName: self.transformerName)?.reverseTransformedValue(fieldValue) as? Data + } + + public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? { + + return ValueTransformer(forName: self.transformerName)?.transformedValue(data) as! FieldStoredValue? + } + + + // MARK: Internal + + internal static var transformerName: NSValueTransformerName { + + if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) { + + return .secureUnarchiveFromDataTransformerName + } + else { + + return .keyedUnarchiveFromDataTransformerName + } + } + } +} + + +public protocol DefaultNSSecureCodable: NSObject, NSSecureCoding {} + +extension NSArray: DefaultNSSecureCodable {} + +extension NSDictionary: DefaultNSSecureCodable {} + +extension NSSet: DefaultNSSecureCodable {} + +extension NSString: DefaultNSSecureCodable {} + +extension NSNumber: DefaultNSSecureCodable {} + +extension NSDate: DefaultNSSecureCodable {} + +extension NSData: DefaultNSSecureCodable {} + +extension NSURL: DefaultNSSecureCodable {} + +extension NSUUID: DefaultNSSecureCodable {} + +extension NSNull: DefaultNSSecureCodable {} diff --git a/Sources/FieldCoders.Json.swift b/Sources/FieldCoders.Json.swift new file mode 100644 index 0000000..02de3a3 --- /dev/null +++ b/Sources/FieldCoders.Json.swift @@ -0,0 +1,59 @@ +// +// FieldCoders.Json.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 Foundation + + +// MARK: - FieldCoders + +extension FieldCoders { + + // MARK: - Json + + public struct Json: FieldCoderType { + + // MARK: FieldCoderType + + public typealias FieldStoredValue = V + + public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? { + + guard let fieldValue = fieldValue else { + + return nil + } + return try? JSONEncoder().encode(fieldValue) + } + + public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? { + + guard let data = data else { + + return nil + } + return try? JSONDecoder().decode(FieldStoredValue.self, from: data) + } + } +} diff --git a/Sources/FieldCoders.NSCoding.swift b/Sources/FieldCoders.NSCoding.swift new file mode 100644 index 0000000..672e8d3 --- /dev/null +++ b/Sources/FieldCoders.NSCoding.swift @@ -0,0 +1,91 @@ +// +// FieldCoders.NSCoding.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 Foundation + + +// MARK: - FieldCoders + +extension FieldCoders { + + // MARK: - NSCoding + + public struct NSCoding: FieldCoderType { + + // MARK: FieldCoderType + + public typealias FieldStoredValue = V + + public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? { + + guard let fieldValue = fieldValue else { + + return nil + } + if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) { + + return try! NSKeyedArchiver.archivedData( + withRootObject: fieldValue, + requiringSecureCoding: self.requiresSecureCoding + ) + } + else { + + return NSKeyedArchiver.archivedData(withRootObject: fieldValue) + } + } + + public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? { + + guard let data = data else { + + return nil + } + if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) { + + return try! NSKeyedUnarchiver.unarchivedObject(ofClass: FieldStoredValue.self, from: data) + } + else { + + return NSKeyedUnarchiver.unarchiveObject(with: data) as! FieldStoredValue? + } + } + + + // MARK: Private + + private static var requiresSecureCoding: Bool { + + switch FieldStoredValue.self { + + case let valueType as NSSecureCoding.Type: + return valueType.supportsSecureCoding + + default: + return false + } + } + } +} diff --git a/Sources/FieldCoders.Plist.swift b/Sources/FieldCoders.Plist.swift new file mode 100644 index 0000000..ffc1908 --- /dev/null +++ b/Sources/FieldCoders.Plist.swift @@ -0,0 +1,59 @@ +// +// FieldCoders.Plist.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 Foundation + + +// MARK: - FieldCoders + +extension FieldCoders { + + // MARK: - Plist + + public struct Plist: FieldCoderType { + + // MARK: FieldCoderType + + public typealias FieldStoredValue = V + + public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? { + + guard let fieldValue = fieldValue else { + + return nil + } + return try? PropertyListEncoder().encode(fieldValue) + } + + public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? { + + guard let data = data else { + + return nil + } + return try? PropertyListDecoder().decode(FieldStoredValue.self, from: data) + } + } +} diff --git a/Sources/FieldCoders.swift b/Sources/FieldCoders.swift new file mode 100644 index 0000000..219cb36 --- /dev/null +++ b/Sources/FieldCoders.swift @@ -0,0 +1,30 @@ +// +// FieldCoders.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. +// + + +/** + Namespace for Built-in Field Coders + */ +public enum FieldCoders {} diff --git a/Sources/FieldProtocol.swift b/Sources/FieldProtocol.swift index e38e642..9ae3014 100644 --- a/Sources/FieldProtocol.swift +++ b/Sources/FieldProtocol.swift @@ -34,24 +34,3 @@ internal protocol FieldProtocol: PropertyProtocol { static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) } - - -// MARK: - FieldAttributeProtocol - -internal protocol FieldAttributeProtocol: FieldProtocol { - - typealias EntityDescriptionValues = ( - attributeType: NSAttributeType, - isOptional: Bool, - isTransient: Bool, - allowsExternalBinaryDataStorage: Bool, - versionHashModifier: String?, - renamingIdentifier: String?, - affectedByKeyPaths: Set, - defaultValue: Any? - ) - - var entityDescriptionValues: () -> EntityDescriptionValues { get } - var getter: CoreStoreManagedObject.CustomGetter? { get } - var setter: CoreStoreManagedObject.CustomSetter? { get } -} diff --git a/Sources/Internals.AnyFieldCoder.swift b/Sources/Internals.AnyFieldCoder.swift new file mode 100644 index 0000000..a59482e --- /dev/null +++ b/Sources/Internals.AnyFieldCoder.swift @@ -0,0 +1,222 @@ +// +// Internals.AnyFieldCoder.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 Foundation + + +// MARK: - Internals + +extension Internals { + + // MARK: - AnyFieldCoder + + internal struct AnyFieldCoder { + + // MARK: Internal + + internal let transformerName: NSValueTransformerName + internal let transformer: Foundation.ValueTransformer + internal let encodeToStoredData: (_ fieldValue: Any?) -> Data? + internal let decodeFromStoredData: (_ data: Data?) -> Any? + + internal init(_ fieldCoder: Coder.Type) { + + let transformer = CustomValueTransformer(fieldCoder: fieldCoder) + self.transformerName = transformer.id + self.transformer = transformer + self.encodeToStoredData = { + + switch $0 { + + case let value as Coder.FieldStoredValue: + return fieldCoder.encodeToStoredData(value) + + case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox: + return fieldCoder.encodeToStoredData(valueBox.value as! Coder.FieldStoredValue?) + + default: + return fieldCoder.encodeToStoredData(nil) + } + } + self.decodeFromStoredData = { fieldCoder.decodeFromStoredData($0) } + } + + internal init(tag: UUID, encode: @escaping (V) -> Data?, decode: @escaping (Data?) -> V) { + + let transformer = CustomValueTransformer(tag: tag) + self.transformerName = transformer.id + self.transformer = transformer + self.encodeToStoredData = { encode($0 as! V) } + self.decodeFromStoredData = { decode($0) } + } + + internal func register() { + + let transformerName = self.transformerName + if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) { + + if transformerName == .secureUnarchiveFromDataTransformerName { + + return + } + } + switch transformerName { + + case .keyedUnarchiveFromDataTransformerName, + .unarchiveFromDataTransformerName, + .isNotNilTransformerName, + .isNilTransformerName, + .negateBooleanTransformerName: + return + + case let transformerName: + Self.cachedCoders[transformerName] = self + + Foundation.ValueTransformer.setValueTransformer( + self.transformer, + forName: transformerName + ) + } + } + + + // MARK: FilePrivate + + fileprivate static var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:] + + + // MARK: - TransformableDefaultValueCodingBox + + @objc(_CoreStore_Internals_TransformableDefaultValueCodingBox) + internal final class TransformableDefaultValueCodingBox: NSObject, NSSecureCoding { + + // MARK: Internal + + @objc + internal dynamic let transformerName: String + + @objc + internal dynamic let data: Data + + @nonobjc + internal let value: Any? + + internal init?(defaultValue: Any?, fieldCoder: Internals.AnyFieldCoder?) { + + guard + let fieldCoder = fieldCoder, + let defaultValue = defaultValue, + !(defaultValue is NSNull), + let data = fieldCoder.encodeToStoredData(defaultValue) + else { + + return nil + } + self.transformerName = fieldCoder.transformerName.rawValue + self.value = defaultValue + self.data = data + } + + + // MARK: NSSecureCoding + + @objc + dynamic class var supportsSecureCoding: Bool { + + return true + } + + + // MARK: NSCoding + + @objc + dynamic required init?(coder aDecoder: NSCoder) { + + guard + case let transformerName as String = aDecoder.decodeObject(forKey: #keyPath(TransformableDefaultValueCodingBox.transformerName)), + let transformer = ValueTransformer(forName: .init(transformerName)), + case let data as Data = aDecoder.decodeObject(forKey: #keyPath(TransformableDefaultValueCodingBox.data)), + let value = transformer.reverseTransformedValue(data) + else { + + return nil + } + self.transformerName = transformerName + self.data = data + self.value = value + } + + @objc + dynamic func encode(with coder: NSCoder) { + + coder.encode(self.data, forKey: #keyPath(TransformableDefaultValueCodingBox.data)) + coder.encode(self.transformerName, forKey: #keyPath(TransformableDefaultValueCodingBox.transformerName)) + } + } + } + + + // MARK: - CustomValueTransformer + + fileprivate final class CustomValueTransformer: ValueTransformer { + + // MARK: FilePrivate + + fileprivate let id: NSValueTransformerName + + fileprivate init(fieldCoder: Coder.Type) { + + self.id = .init(rawValue: "CoreStore.FieldCoders.CustomValueTransformer<\(String(reflecting: fieldCoder))>.transformerName") + } + + fileprivate init(tag: UUID) { + + self.id = .init(rawValue: "CoreStore.FieldCoders.CustomValueTransformer<\(tag.uuidString)>.transformerName") + } + + + // MARK: ValueTransformer + + override class func transformedValueClass() -> AnyClass { + + return NSData.self + } + + override class func allowsReverseTransformation() -> Bool { + + return true + } + + override func transformedValue(_ value: Any?) -> Any? { + + return AnyFieldCoder.cachedCoders[self.id]?.encodeToStoredData(value) as Data? + } + + override func reverseTransformedValue(_ value: Any?) -> Any? { + + return AnyFieldCoder.cachedCoders[self.id]?.decodeFromStoredData(value as! Data?) + } + } +} diff --git a/Sources/NSEntityDescription+Migration.swift b/Sources/NSEntityDescription+Migration.swift index 801ce25..4ba9c26 100644 --- a/Sources/NSEntityDescription+Migration.swift +++ b/Sources/NSEntityDescription+Migration.swift @@ -33,29 +33,49 @@ extension NSEntityDescription { @nonobjc internal func cs_resolveAttributeNames() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] { - return self.attributesByName.reduce(into: [:], { (result, attribute: (name: String, description: NSAttributeDescription)) in - result[attribute.name] = (attribute.description, attribute.description.versionHash) - }) + + return self.attributesByName.reduce( + into: [:], + { (result, attribute: (name: String, description: NSAttributeDescription)) in + + result[attribute.name] = (attribute.description, attribute.description.versionHash) + } + ) } @nonobjc internal func cs_resolveAttributeRenamingIdentities() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] { - return self.attributesByName.reduce(into: [:], { (result, attribute: (name: String, description: NSAttributeDescription)) in - result[attribute.description.renamingIdentifier ?? attribute.name] = (attribute.description, attribute.description.versionHash) - }) + + return self.attributesByName.reduce( + into: [:], + { (result, attribute: (name: String, description: NSAttributeDescription)) in + + result[attribute.description.renamingIdentifier ?? attribute.name] = (attribute.description, attribute.description.versionHash) + } + ) } @nonobjc internal func cs_resolveRelationshipNames() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] { - return self.relationshipsByName.reduce(into: [:], { (result, relationship: (name: String, description: NSRelationshipDescription)) in - result[relationship.name] = (relationship.description, relationship.description.versionHash) - }) + + return self.relationshipsByName.reduce( + into: [:], + { (result, relationship: (name: String, description: NSRelationshipDescription)) in + + result[relationship.name] = (relationship.description, relationship.description.versionHash) + } + ) } @nonobjc internal func cs_resolveRelationshipRenamingIdentities() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] { - return self.relationshipsByName.reduce(into: [:], { (result, relationship: (name: String, description: NSRelationshipDescription)) in - result[relationship.description.renamingIdentifier ?? relationship.name] = (relationship.description, relationship.description.versionHash) - }) + + return self.relationshipsByName.reduce( + into: [:], + { (result, relationship: (name: String, description: NSRelationshipDescription)) in + + result[relationship.description.renamingIdentifier ?? relationship.name] = (relationship.description, relationship.description.versionHash) + } + ) } } diff --git a/Sources/NSManagedObjectModel+Migration.swift b/Sources/NSManagedObjectModel+Migration.swift index 013fd2b..d602320 100644 --- a/Sources/NSManagedObjectModel+Migration.swift +++ b/Sources/NSManagedObjectModel+Migration.swift @@ -33,15 +33,25 @@ extension NSManagedObjectModel { @nonobjc internal func cs_resolveNames() -> [String: (entity: NSEntityDescription, versionHash: Data)] { - return self.entitiesByName.reduce(into: [:], { (result, entity: (name: String, description: NSEntityDescription)) in - result[entity.name] = (entity.description, entity.description.versionHash) - }) + + return self.entitiesByName.reduce( + into: [:], + { (result, entity: (name: String, description: NSEntityDescription)) in + + result[entity.name] = (entity.description, entity.description.versionHash) + } + ) } @nonobjc internal func cs_resolveRenamingIdentities() -> [String: (entity: NSEntityDescription, versionHash: Data)] { - return self.entitiesByName.reduce(into: [:], { (result, entity: (name: String, description: NSEntityDescription)) in - result[entity.description.renamingIdentifier ?? entity.name] = (entity.description, entity.description.versionHash) - }) + + return self.entitiesByName.reduce( + into: [:], + { (result, entity: (name: String, description: NSEntityDescription)) in + + result[entity.description.renamingIdentifier ?? entity.name] = (entity.description, entity.description.versionHash) + } + ) } } diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift index be8c345..035b817 100644 --- a/Sources/PartialObject.swift +++ b/Sources/PartialObject.swift @@ -65,8 +65,10 @@ public struct PartialObject { case let value as V: return value - default: - return nil as Any? as! V // filter NSNull + case nil, + is NSNull, + _? /* any other unrelated type */ : + return nil as Any? as! V // There should be no uninitialized state at this point } } @@ -87,15 +89,41 @@ public struct PartialObject { This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage. */ - public func primitiveValue(for property: (O) -> FieldContainer.Computed) -> V { + public func primitiveValue(for property: (O) -> FieldContainer.Computed) -> V? { switch self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) { case let value as V: return value - default: - return nil as Any? as! V // filter NSNull + case nil, + is NSNull, + _? /* any other unrelated type */ : + return nil + } + } + + /** + Returns the value for the specified property from the managed object’s private internal storage. + + This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage. + */ + public func primitiveValue(for property: (O) -> FieldContainer.Coded) -> V? { + + let keyPath = property(O.meta).keyPath + switch self.rawObject.primitiveValue(forKey: keyPath) { + + case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox: + rawObject.setPrimitiveValue(valueBox.value, forKey: keyPath) + return valueBox.value as? V + + case let value as V: + return value + + case nil, + is NSNull, + _? /* any other unrelated type */ : + return nil } }