diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 3fac3b2..1815a05 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -322,7 +322,6 @@ B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */; }; B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */; }; B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */; }; - B53304AA230BA4F7007C2BD8 /* DynamicObjectMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53304A9230BA4F7007C2BD8 /* DynamicObjectMeta.swift */; }; B533C4DB1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */; }; B533C4DC1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */; }; B533C4DD1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */; }; @@ -548,6 +547,34 @@ B56924021EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56923FE1EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift */; }; B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */; }; B56965241B356B820075EE4A /* MigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965231B356B820075EE4A /* MigrationResult.swift */; }; + B56E4ECA23CD9B4800E1708C /* Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EC923CD9B4800E1708C /* Field.swift */; }; + B56E4ECB23CD9B4800E1708C /* Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EC923CD9B4800E1708C /* Field.swift */; }; + B56E4ECC23CD9B4800E1708C /* Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EC923CD9B4800E1708C /* Field.swift */; }; + B56E4ECD23CD9B4800E1708C /* Field.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EC923CD9B4800E1708C /* Field.swift */; }; + B56E4ECF23CD9E4200E1708C /* Field.Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */; }; + B56E4ED023CD9E4200E1708C /* Field.Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */; }; + B56E4ED123CD9E4200E1708C /* Field.Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */; }; + B56E4ED223CD9E4200E1708C /* Field.Stored.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */; }; + B56E4ED423CDB54A00E1708C /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */; }; + B56E4ED523CDB54A00E1708C /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */; }; + B56E4ED623CDB54A00E1708C /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */; }; + B56E4ED723CDB54A00E1708C /* FieldProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */; }; + B56E4ED923CEB8E700E1708C /* FieldStorableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */; }; + B56E4EDA23CEB8E700E1708C /* FieldStorableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */; }; + B56E4EDB23CEB8E700E1708C /* FieldStorableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */; }; + B56E4EDC23CEB8E700E1708C /* FieldStorableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */; }; + B56E4EDF23CEBCF000E1708C /* FieldOptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */; }; + B56E4EE023CEBCF000E1708C /* FieldOptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */; }; + B56E4EE123CEBCF000E1708C /* FieldOptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */; }; + B56E4EE223CEBCF000E1708C /* FieldOptionalType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */; }; + B56E4EE423CEDF0900E1708C /* Field.Computed.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Computed.swift */; }; + 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 */; }; @@ -979,7 +1006,6 @@ B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeDataModelSchema.swift; sourceTree = ""; }; B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreSchema.swift; sourceTree = ""; }; B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Logging.swift"; sourceTree = ""; }; - B53304A9230BA4F7007C2BD8 /* DynamicObjectMeta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicObjectMeta.swift; sourceTree = ""; }; B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+CoreStore.swift"; sourceTree = ""; }; B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = ""; }; B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreManagedObject.swift; sourceTree = ""; }; @@ -1038,6 +1064,13 @@ B56923FE1EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSUnsafeDataModelSchema.swift; sourceTree = ""; }; B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "DataStack+Migration.swift"; sourceTree = ""; }; B56965231B356B820075EE4A /* MigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationResult.swift; sourceTree = ""; }; + B56E4EC923CD9B4800E1708C /* Field.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.swift; sourceTree = ""; }; + B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Stored.swift; sourceTree = ""; }; + B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldProtocol.swift; sourceTree = ""; }; + 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 = ""; }; @@ -1386,6 +1419,7 @@ children = ( B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */, B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */, + B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */, B50564D22350CC3100482308 /* PropertyProtocol.swift */, B53D9E5823513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift */, B50E175623517DE4004F033C /* Differentiable.swift */, @@ -1513,13 +1547,34 @@ name = Migrating; sourceTree = ""; }; + B56E4EC823CD9B2E00E1708C /* Field Properties */ = { + isa = PBXGroup; + children = ( + B56E4EC923CD9B4800E1708C /* Field.swift */, + B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */, + B56E4EE323CEDF0900E1708C /* Field.Computed.swift */, + B56E4EE823CF0C4500E1708C /* Field.PlistCoded.swift */, + B56E4EDD23CEBB0400E1708C /* Supported Values */, + ); + name = "Field Properties"; + sourceTree = ""; + }; + B56E4EDD23CEBB0400E1708C /* Supported Values */ = { + isa = PBXGroup; + children = ( + B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */, + B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */, + ); + name = "Supported Values"; + sourceTree = ""; + }; B57358D71E5A7F9B0094B50A /* Dynamic Models */ = { isa = PBXGroup; children = ( B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */, - B53304A9230BA4F7007C2BD8 /* DynamicObjectMeta.swift */, B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */, - B5831B6E1F3355C300A9F647 /* Properties */, + B56E4EC823CD9B2E00E1708C /* Field Properties */, + B5831B6E1F3355C300A9F647 /* Legacy Properties */, B52F74391E9B8724005F3DAC /* Dynamic Schema */, B5D339DC1E9489C700C880DE /* DynamicObject.swift */, B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */, @@ -1529,7 +1584,7 @@ name = "Dynamic Models"; sourceTree = ""; }; - B5831B6E1F3355C300A9F647 /* Properties */ = { + B5831B6E1F3355C300A9F647 /* Legacy Properties */ = { isa = PBXGroup; children = ( B5D33A001E96012400C880DE /* Relationship.swift */, @@ -1543,7 +1598,7 @@ B509D7D223C84E1900F42824 /* Transformable.Required.swift */, B509D7D723C84E2600F42824 /* Transformable.Optional.swift */, ); - name = Properties; + name = "Legacy Properties"; sourceTree = ""; }; B5A1DAC61F111BBE003CF369 /* KeyPath Utilities */ = { @@ -2145,6 +2200,7 @@ files = ( B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */, B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */, + B56E4ED423CDB54A00E1708C /* FieldProtocol.swift in Sources */, B509D7C423C848DA00F42824 /* Relationship.ToOne.swift in Sources */, B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */, B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */, @@ -2194,6 +2250,7 @@ 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 */, @@ -2213,10 +2270,12 @@ B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */, B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */, B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */, + B56E4EDF23CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */, B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */, B51260931E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, + B56E4ECA23CD9B4800E1708C /* Field.swift in Sources */, B5DAFB482203D9F8003FCCD0 /* Where.Expression.swift in Sources */, B509D7D823C84E2600F42824 /* Transformable.Optional.swift in Sources */, B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */, @@ -2254,7 +2313,6 @@ B5E1B5A21CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, B50E175723517DE4004F033C /* Differentiable.swift in Sources */, - B53304AA230BA4F7007C2BD8 /* DynamicObjectMeta.swift in Sources */, B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */, B5BF7FC6234D7E460070E741 /* ObjectSnapshot.swift in Sources */, @@ -2271,9 +2329,11 @@ B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */, B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */, B5215CA91FA4810300139E3A /* QueryChainBuilder.swift in Sources */, + B56E4EE423CEDF0900E1708C /* Field.Computed.swift in Sources */, B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, B5E222231CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */, B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */, + B56E4ECF23CD9E4200E1708C /* Field.Stored.swift in Sources */, B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B5E2222A1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, @@ -2314,6 +2374,7 @@ B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */, B5E8A72021C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */, B53D9E5923513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift in Sources */, + B56E4ED923CEB8E700E1708C /* FieldStorableType.swift in Sources */, B5474D152227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, B56923FF1EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */, B5215CAE1FA4812500139E3A /* SectionMonitorBuilder.swift in Sources */, @@ -2429,6 +2490,7 @@ 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 */, @@ -2448,6 +2510,7 @@ B5831F432212700400D8604C /* Where.Expression.swift in Sources */, B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, B5FE4DA81C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, + B56E4EE023CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B5F8496D234898240029D57B /* ListSnapshot.swift in Sources */, B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */, @@ -2475,6 +2538,7 @@ B5D8CA772346EAEE0055D7D1 /* DataStack+DataSources.swift in Sources */, 82BA18D01C4BBD7100A0916E /* Internals.MigrationManager.swift in Sources */, B5DE5231230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */, + B56E4ED523CDB54A00E1708C /* FieldProtocol.swift in Sources */, B52F74461E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, 82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */, B59851491C90289D00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, @@ -2505,6 +2569,7 @@ B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */, B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, + B56E4EE523CEDF0900E1708C /* Field.Computed.swift in Sources */, B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, 82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */, B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, @@ -2549,6 +2614,7 @@ B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, B501322B2346A9AE00FC238B /* ListPublisher.swift in Sources */, B56924001EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */, + B56E4EDA23CEB8E700E1708C /* FieldStorableType.swift in Sources */, B5215CAF1FA4812500139E3A /* SectionMonitorBuilder.swift in Sources */, 82BA18D61C4BBD7100A0916E /* NSManagedObjectContext+Transaction.swift in Sources */, B56923ED1EB827F6007C4DC9 /* SchemaMappingProvider.swift in Sources */, @@ -2564,8 +2630,10 @@ B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */, 82BA18CC1C4BBD6400A0916E /* Progress+Convenience.swift in Sources */, B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */, + B56E4ED023CD9E4200E1708C /* Field.Stored.swift in Sources */, 82BA18C01C4BBD5300A0916E /* DataStack+Observing.swift in Sources */, 82BA18A61C4BBD2900A0916E /* DefaultLogger.swift in Sources */, + B56E4ECB23CD9B4800E1708C /* Field.swift in Sources */, B509D7BE23C8480A00F42824 /* Value.Optional.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2662,6 +2730,7 @@ 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 */, @@ -2681,6 +2750,7 @@ B52DD1BD1BE1F94300949AFE /* NSManagedObject+Convenience.swift in Sources */, B5831F4222126FED00D8604C /* KeyPathGenericBindings.swift in Sources */, B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */, + B56E4EE223CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */, B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */, @@ -2708,6 +2778,7 @@ B52DD1B81BE1F94000949AFE /* DataStack+Migration.swift in Sources */, B5ECDC091CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5D8CA792346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */, + B56E4ED723CDB54A00E1708C /* FieldProtocol.swift in Sources */, B56923C71EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */, B5DE5233230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */, B52DD1A51BE1F92F00949AFE /* ImportableUniqueObject.swift in Sources */, @@ -2738,6 +2809,7 @@ B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */, B50E175A23517DE4004F033C /* Differentiable.swift in Sources */, B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, + B56E4EE723CEDF0900E1708C /* Field.Computed.swift in Sources */, B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */, B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */, B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, @@ -2782,6 +2854,7 @@ B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */, B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, B501322E2346A9B100FC238B /* ListPublisher.swift in Sources */, + B56E4EDC23CEB8E700E1708C /* FieldStorableType.swift in Sources */, B56924021EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */, B5215CB11FA4812500139E3A /* SectionMonitorBuilder.swift in Sources */, B5220E171D1306DF009BC71E /* UnsafeDataTransaction+Observing.swift in Sources */, @@ -2797,8 +2870,10 @@ B5D339EA1E9493A500C880DE /* Entity.swift in Sources */, B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */, B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */, + B56E4ED223CD9E4200E1708C /* Field.Stored.swift in Sources */, B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, + B56E4ECD23CD9B4800E1708C /* Field.swift in Sources */, B509D7C223C8480B00F42824 /* Value.Optional.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2895,6 +2970,7 @@ 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 */, @@ -2914,6 +2990,7 @@ B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */, B5FE4DA91C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B5831F442212700500D8604C /* Where.Expression.swift in Sources */, + B56E4EE123CEBCF000E1708C /* FieldOptionalType.swift in Sources */, B5F8496E234898240029D57B /* ListSnapshot.swift in Sources */, B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */, B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, @@ -2941,6 +3018,7 @@ B5D8CA782346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */, B56923C61EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */, B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */, + B56E4ED623CDB54A00E1708C /* FieldProtocol.swift in Sources */, B5DE5232230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */, B598514A1C90289E00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B52F74471E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */, @@ -2971,6 +3049,7 @@ B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */, B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */, B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */, + B56E4EE623CEDF0900E1708C /* Field.Computed.swift in Sources */, B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */, B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */, @@ -3015,6 +3094,7 @@ B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */, B501322D2346A9B000FC238B /* ListPublisher.swift in Sources */, B56924011EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */, + B56E4EDB23CEB8E700E1708C /* FieldStorableType.swift in Sources */, B5215CB01FA4812500139E3A /* SectionMonitorBuilder.swift in Sources */, B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */, B56923EE1EB827F6007C4DC9 /* SchemaMappingProvider.swift in Sources */, @@ -3030,8 +3110,10 @@ B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */, B56321A41BD65216006C9394 /* CoreStore+Migration.swift in Sources */, B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */, + B56E4ED123CD9E4200E1708C /* Field.Stored.swift in Sources */, B56321A01BD65216006C9394 /* ObjectObserver.swift in Sources */, B56321951BD65216006C9394 /* TypeErasedClauses.swift in Sources */, + B56E4ECC23CD9B4800E1708C /* Field.swift in Sources */, B509D7C023C8480B00F42824 /* Value.Optional.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CoreStoreTests/DynamicModelTests.swift b/CoreStoreTests/DynamicModelTests.swift index 2b40a97..99f4e3a 100644 --- a/CoreStoreTests/DynamicModelTests.swift +++ b/CoreStoreTests/DynamicModelTests.swift @@ -37,40 +37,38 @@ import CoreStore #endif class Animal: CoreStoreObject { - - let species = Value.Required("species", initial: "Swift") + + @Field.Stored("species") + var species: String = "Swift" + let master = Relationship.ToOne("master") let color = Transformable.Optional("color") } class Dog: Animal { - - let nickname = Value.Optional("nickname") + + @Field.Stored("nickname") + var nickname: String? + let age = Value.Required("age", initial: 1) let friends = Relationship.ToManyOrdered("friends") let friendedBy = Relationship.ToManyUnordered("friendedBy", inverse: { $0.friends }) } class Person: CoreStoreObject { - - let title = Value.Required( - "title", - initial: "Mr.", - customSetter: Person.setTitle - ) - - let name = Value.Required( - "name", - initial: "", - customSetter: Person.setName - ) - - let displayName = Value.Optional( + + @Field.Stored("title", customSetter: Person.setTitle(_:_:)) + var title: String = "Mr." + + @Field.Stored("name", customSetter: Person.setName(_:_:)) + var name: String = "" + + @Field.Computed( "displayName", - isTransient: true, customGetter: Person.getDisplayName(_:), affectedByKeyPaths: Person.keyPathsAffectingDisplayName() ) + var displayName: String? let spouse = Relationship.ToOne("spouse") @@ -81,34 +79,34 @@ class Person: CoreStoreObject { private static func setTitle(_ partialObject: PartialObject, _ newValue: String) { - partialObject.setPrimitiveValue(newValue, for: { $0.title }) - partialObject.setPrimitiveValue(nil, for: { $0.displayName }) + partialObject.setPrimitiveValue(newValue, for: \.$title) + partialObject.setPrimitiveValue(nil, for: { $0.$displayName }) } private static func setName(_ partialObject: PartialObject, _ newValue: String) { - partialObject.setPrimitiveValue(newValue, for: { $0.name }) - partialObject.setPrimitiveValue(nil, for: { $0.displayName }) + partialObject.setPrimitiveValue(newValue, for: \.$name) + partialObject.setPrimitiveValue(nil, for: \.$displayName) } static func getDisplayName(_ partialObject: PartialObject) -> String? { - if let displayName = partialObject.primitiveValue(for: { $0.displayName }) { + if let displayName = partialObject.primitiveValue(for: \.$displayName) { return displayName } - let title = partialObject.value(for: { $0.title }) - let name = partialObject.value(for: { $0.name }) + let title = partialObject.value(for: \.$title) + let name = partialObject.value(for: \.$name) let displayName = "\(title) \(name)" - partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) + partialObject.setPrimitiveValue(displayName, for: \.$displayName) return displayName } static func keyPathsAffectingDisplayName() -> Set { return [ - String(keyPath: \Person.title), - String(keyPath: \Person.name) + String(keyPath: \Person.$title), + String(keyPath: \Person.$name) ] } } @@ -138,13 +136,13 @@ class DynamicModelTests: BaseTestDataTestCase { ) self.prepareStack(dataStack, configurations: [nil]) { (stack) in - let k1 = String(keyPath: \Animal.species) + let k1 = String(keyPath: \Animal.$species) XCTAssertEqual(k1, "species") - let k2 = String(keyPath: \Dog.species) + let k2 = String(keyPath: \Dog.$species) XCTAssertEqual(k2, "species") - let k3 = String(keyPath: \Dog.nickname) + let k3 = String(keyPath: \Dog.$nickname) XCTAssertEqual(k3, "nickname") let updateDone = self.expectation(description: "update-done") @@ -156,11 +154,11 @@ class DynamicModelTests: BaseTestDataTestCase { asynchronous: { (transaction) in let animal = transaction.create(Into()) - XCTAssertEqual(animal.species.value, "Swift") - XCTAssertTrue(type(of: animal.species.value) == String.self) + XCTAssertEqual(animal.species, "Swift") + XCTAssertTrue(type(of: animal.species) == String.self) - animal.species .= "Sparrow" - XCTAssertEqual(animal.species.value, "Sparrow") + animal.species = "Sparrow" + XCTAssertEqual(animal.species, "Sparrow") animal.color .= .yellow XCTAssertEqual(animal.color.value, Color.yellow) @@ -169,8 +167,8 @@ class DynamicModelTests: BaseTestDataTestCase { switch property.keyPath { - case String(keyPath: \Animal.species): - XCTAssertTrue(property is ValueContainer.Required) + case String(keyPath: \Animal.$species): + XCTAssertTrue(property is FieldContainer.Stored) case String(keyPath: \Animal.master): XCTAssertTrue(property is RelationshipContainer.ToOne) @@ -184,16 +182,16 @@ class DynamicModelTests: BaseTestDataTestCase { } let dog = transaction.create(Into()) - XCTAssertEqual(dog.species.value, "Swift") - XCTAssertEqual(dog.nickname.value, nil) + XCTAssertEqual(dog.species, "Swift") + XCTAssertEqual(dog.nickname, nil) XCTAssertEqual(dog.age.value, 1) for property in Dog.metaProperties(includeSuperclasses: true) { switch property.keyPath { - case String(keyPath: \Dog.species): - XCTAssertTrue(property is ValueContainer.Required) + case String(keyPath: \Dog.$species): + XCTAssertTrue(property is FieldContainer.Stored) case String(keyPath: \Dog.master): XCTAssertTrue(property is RelationshipContainer.ToOne) @@ -201,8 +199,8 @@ class DynamicModelTests: BaseTestDataTestCase { case String(keyPath: \Dog.color): XCTAssertTrue(property is TransformableContainer.Optional) - case String(keyPath: \Dog.nickname): - XCTAssertTrue(property is ValueContainer.Optional) + case String(keyPath: \Dog.$nickname): + XCTAssertTrue(property is FieldContainer.Stored) case String(keyPath: \Dog.age): XCTAssertTrue(property is ValueContainer.Required) @@ -231,17 +229,17 @@ class DynamicModelTests: BaseTestDataTestCase { // // #endif - let didSetObserver = dog.species.observe(options: [.new, .old]) { (object, change) in + let didSetObserver = dog.observe(\.$species, options: [.new, .old]) { (object, change) in XCTAssertEqual(object, dog) XCTAssertEqual(change.kind, .setting) XCTAssertEqual(change.newValue, "Dog") XCTAssertEqual(change.oldValue, "Swift") XCTAssertFalse(change.isPrior) - XCTAssertEqual(object.species.value, "Dog") + XCTAssertEqual(object.species, "Dog") didSetObserverDone.fulfill() } - let willSetObserver = dog.species.observe(options: [.new, .old, .prior]) { (object, change) in + let willSetObserver = dog.observe(\.$species, options: [.new, .old, .prior]) { (object, change) in XCTAssertEqual(object, dog) XCTAssertEqual(change.kind, .setting) @@ -250,25 +248,25 @@ class DynamicModelTests: BaseTestDataTestCase { if change.isPrior { XCTAssertNil(change.newValue) - XCTAssertEqual(object.species.value, "Swift") + XCTAssertEqual(object.species, "Swift") willSetPriorObserverDone.fulfill() } else { XCTAssertEqual(change.newValue, "Dog") - XCTAssertEqual(object.species.value, "Dog") + XCTAssertEqual(object.species, "Dog") willSetNotPriorObserverDone.fulfill() } } - dog.species .= "Dog" - XCTAssertEqual(dog.species.value, "Dog") + dog.species = "Dog" + XCTAssertEqual(dog.species, "Dog") didSetObserver.invalidate() willSetObserver.invalidate() - dog.nickname .= "Spot" - XCTAssertEqual(dog.nickname.value, "Spot") + dog.nickname = "Spot" + XCTAssertEqual(dog.nickname, "Spot") let person = transaction.create(Into()) XCTAssertTrue(person.pets.value.isEmpty) @@ -280,7 +278,7 @@ class DynamicModelTests: BaseTestDataTestCase { ["title", "name"] ) - person.name .= "Joe" + person.name = "Joe" XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe") XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe") @@ -288,35 +286,35 @@ class DynamicModelTests: BaseTestDataTestCase { person.rawObject!.setValue("AAAA", forKey: "displayName") XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA") - person.name .= "John" - XCTAssertEqual(person.name.value, "John") - XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter + person.name = "John" + XCTAssertEqual(person.name, "John") + XCTAssertEqual(person.displayName, "Mr. John") // Custom getter let personSnapshot1 = person.asSnapshot(in: transaction)! - XCTAssertEqual(person.name.value, personSnapshot1.name) - XCTAssertEqual(person.title.value, personSnapshot1.title) - XCTAssertEqual(person.displayName.value, personSnapshot1.displayName) + XCTAssertEqual(person.name, personSnapshot1.$name) + XCTAssertEqual(person.title, personSnapshot1.$title) + XCTAssertEqual(person.displayName, personSnapshot1.$displayName) - person.title .= "Sir" - XCTAssertEqual(person.displayName.value, "Sir John") + person.title = "Sir" + XCTAssertEqual(person.displayName, "Sir John") - XCTAssertEqual(personSnapshot1.name, "John") - XCTAssertEqual(personSnapshot1.title, "Mr.") - XCTAssertEqual(personSnapshot1.displayName, "Mr. John") + XCTAssertEqual(personSnapshot1.$name, "John") + XCTAssertEqual(personSnapshot1.$title, "Mr.") + XCTAssertEqual(personSnapshot1.$displayName, "Mr. John") let personSnapshot2 = person.asSnapshot(in: transaction)! - XCTAssertEqual(person.name.value, personSnapshot2.name) - XCTAssertEqual(person.title.value, personSnapshot2.title) - XCTAssertEqual(person.displayName.value, personSnapshot2.displayName) + XCTAssertEqual(person.name, personSnapshot2.$name) + XCTAssertEqual(person.title, personSnapshot2.$title) + XCTAssertEqual(person.displayName, personSnapshot2.$displayName) var personSnapshot3 = personSnapshot2 - personSnapshot3.name = "James" - XCTAssertEqual(personSnapshot1.name, "John") - XCTAssertEqual(personSnapshot1.displayName, "Mr. John") - XCTAssertEqual(personSnapshot2.name, "John") - XCTAssertEqual(personSnapshot2.displayName, "Sir John") - XCTAssertEqual(personSnapshot3.name, "James") - XCTAssertEqual(personSnapshot3.displayName, "Sir John") + personSnapshot3.$name = "James" + XCTAssertEqual(personSnapshot1.$name, "John") + XCTAssertEqual(personSnapshot1.$displayName, "Mr. John") + XCTAssertEqual(personSnapshot2.$name, "John") + XCTAssertEqual(personSnapshot2.$displayName, "Sir John") + XCTAssertEqual(personSnapshot3.$name, "James") + XCTAssertEqual(personSnapshot3.$displayName, "Sir John") person.pets.value.insert(dog) @@ -338,20 +336,20 @@ class DynamicModelTests: BaseTestDataTestCase { stack.perform( asynchronous: { (transaction) in - let p1 = Where({ $0.species == "Sparrow" }) + let p1 = Where({ $0.$species == "Sparrow" }) XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow")) let bird = try transaction.fetchOne(From(), p1) XCTAssertNotNil(bird) - XCTAssertEqual(bird!.species.value, "Sparrow") + XCTAssertEqual(bird!.species, "Sparrow") - let p2 = Where({ $0.nickname == "Spot" }) + let p2 = Where({ $0.$nickname == "Spot" }) XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot")) - let dog = try transaction.fetchOne(From().where(\.nickname == "Spot")) + let dog = try transaction.fetchOne(From().where(\.$nickname == "Spot")) XCTAssertNotNil(dog) - XCTAssertEqual(dog!.nickname.value, "Spot") - XCTAssertEqual(dog!.species.value, "Dog") + XCTAssertEqual(dog!.nickname, "Spot") + XCTAssertEqual(dog!.species, "Dog") let person = try transaction.fetchOne(From()) XCTAssertNotNil(person) @@ -365,12 +363,12 @@ class DynamicModelTests: BaseTestDataTestCase { _ = try transaction.fetchAll( From() - .where(\Animal.species == "Dog" && \Dog.age == 10) + .where(\Animal.$species == "Dog" && \Dog.age == 10) ) _ = try transaction.fetchAll( From() - .where(\Dog.age == 10 && \Animal.species == "Dog") - .orderBy(.ascending({ $0.species })) + .where(\Dog.age == 10 && \Animal.$species == "Dog") + .orderBy(.ascending({ $0.$species })) ) _ = try transaction.fetchAll( From(), @@ -378,11 +376,11 @@ class DynamicModelTests: BaseTestDataTestCase { ) _ = try transaction.fetchAll( From(), - Where({ $0.species == "Dog" && $0.age == 10 }) + Where({ $0.$species == "Dog" && $0.age == 10 }) ) _ = try transaction.fetchAll( From(), - Where({ $0.age == 10 && $0.species == "Dog" }) + Where({ $0.age == 10 && $0.$species == "Dog" }) ) _ = try transaction.fetchAll( From(), @@ -409,8 +407,8 @@ class DynamicModelTests: BaseTestDataTestCase { @objc dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() { - XCTAssertEqual(String(keyPath: \Animal.species), "species") - XCTAssertEqual(String(keyPath: \Dog.species), "species") + XCTAssertEqual(String(keyPath: \Animal.$species), "species") + XCTAssertEqual(String(keyPath: \Dog.$species), "species") } @nonobjc diff --git a/CoreStoreTests/ListObserverTests.swift b/CoreStoreTests/ListObserverTests.swift index 7257df9..ac1bb5a 100644 --- a/CoreStoreTests/ListObserverTests.swift +++ b/CoreStoreTests/ListObserverTests.swift @@ -53,7 +53,7 @@ class ListObserverTests: BaseTestDataTestCase { var events = 0 - let willChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), object: observer, handler: { (note) -> Bool in @@ -67,7 +67,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 0 } ) - let didInsertSectionExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"), object: observer, handler: { (note) -> Bool in @@ -87,7 +87,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 1 } ) - let didInsertObjectExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"), object: observer, handler: { (note) -> Bool in @@ -119,7 +119,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 2 } ) - let didChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), object: observer, handler: { (note) -> Bool in @@ -184,7 +184,7 @@ class ListObserverTests: BaseTestDataTestCase { var events = 0 - let willChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), object: observer, handler: { (note) -> Bool in @@ -199,7 +199,7 @@ class ListObserverTests: BaseTestDataTestCase { } ) - let didUpdateObjectExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"), object: observer, handler: { (note) -> Bool in @@ -250,7 +250,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 1 || events == 2 } ) - let didChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), object: observer, handler: { (note) -> Bool in @@ -329,7 +329,7 @@ class ListObserverTests: BaseTestDataTestCase { var events = 0 - let willChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), object: observer, handler: { (note) -> Bool in @@ -343,7 +343,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 0 } ) - let didMoveObjectExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"), object: observer, handler: { (note) -> Bool in @@ -376,7 +376,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 1 } ) - let didChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), object: observer, handler: { (note) -> Bool in @@ -437,7 +437,7 @@ class ListObserverTests: BaseTestDataTestCase { var events = 0 - let willChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), object: observer, handler: { (note) -> Bool in @@ -451,7 +451,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 0 } ) - let didUpdateObjectExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"), object: observer, handler: { (note) -> Bool in @@ -480,7 +480,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 1 || events == 2 } ) - let didDeleteSectionExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"), object: observer, handler: { (note) -> Bool in @@ -508,7 +508,7 @@ class ListObserverTests: BaseTestDataTestCase { return events == 3 } ) - let didChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), object: observer, handler: { (note) -> Bool in diff --git a/CoreStoreTests/ObjectObserverTests.swift b/CoreStoreTests/ObjectObserverTests.swift index df92521..4072916 100644 --- a/CoreStoreTests/ObjectObserverTests.swift +++ b/CoreStoreTests/ObjectObserverTests.swift @@ -57,7 +57,7 @@ class ObjectObserverTests: BaseTestDataTestCase { var events = 0 - let willUpdateExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"), object: observer, handler: { (note) -> Bool in @@ -74,7 +74,7 @@ class ObjectObserverTests: BaseTestDataTestCase { return events == 0 } ) - let didUpdateExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"), object: observer, handler: { (note) -> Bool in @@ -154,7 +154,7 @@ class ObjectObserverTests: BaseTestDataTestCase { var events = 0 - let didDeleteExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"), object: observer, handler: { (note) -> Bool in diff --git a/CoreStoreTests/TransactionTests.swift b/CoreStoreTests/TransactionTests.swift index a26e15b..e91bfb7 100644 --- a/CoreStoreTests/TransactionTests.swift +++ b/CoreStoreTests/TransactionTests.swift @@ -400,7 +400,7 @@ final class TransactionTests: BaseTestCase { XCTAssertFalse(monitor.hasObjects()) var events = 0 - let willChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), object: observer, handler: { (note) -> Bool in @@ -414,7 +414,7 @@ final class TransactionTests: BaseTestCase { return events == 0 } ) - let didInsertObjectExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"), object: observer, handler: { (note) -> Bool in @@ -444,7 +444,7 @@ final class TransactionTests: BaseTestCase { return events == 1 } ) - let didChangeExpectation = self.expectation( + _ = self.expectation( forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), object: observer, handler: { (note) -> Bool in diff --git a/CoreStoreTests/WhereTests.swift b/CoreStoreTests/WhereTests.swift index d9b2ed5..9ceec1f 100644 --- a/CoreStoreTests/WhereTests.swift +++ b/CoreStoreTests/WhereTests.swift @@ -109,8 +109,8 @@ final class WhereTests: XCTestCase { ) XCTAssertAllEqual( "master.pets.species", - (\Animal.master ~ \.pets ~ \.species).description, - String(keyPath: \Animal.master ~ \.pets ~ \.species) + (\Animal.master ~ \.pets ~ \.$species).description, + String(keyPath: \Animal.master ~ \.pets ~ \.$species) ) XCTAssertAllEqual( "master.pets.master", @@ -167,8 +167,8 @@ final class WhereTests: XCTestCase { ) XCTAssertAllEqual( "ANY master.pets.species", - (\Animal.master ~ \.pets ~ \.species).any().description, - String(keyPath: (\Animal.master ~ \.pets ~ \.species).any()) + (\Animal.master ~ \.pets ~ \.$species).any().description, + String(keyPath: (\Animal.master ~ \.pets ~ \.$species).any()) ) } } @@ -196,8 +196,8 @@ final class WhereTests: XCTestCase { ) XCTAssertAllEqual( "ALL master.pets.species", - (\Animal.master ~ \.pets ~ \.species).all().description, - String(keyPath: (\Animal.master ~ \.pets ~ \.species).all()) + (\Animal.master ~ \.pets ~ \.$species).all().description, + String(keyPath: (\Animal.master ~ \.pets ~ \.$species).all()) ) } } @@ -225,8 +225,8 @@ final class WhereTests: XCTestCase { ) XCTAssertAllEqual( "NONE master.pets.species", - (\Animal.master ~ \.pets ~ \.species).none().description, - String(keyPath: (\Animal.master ~ \.pets ~ \.species).none()) + (\Animal.master ~ \.pets ~ \.$species).none().description, + String(keyPath: (\Animal.master ~ \.pets ~ \.$species).none()) ) } } @@ -247,7 +247,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.name) == dummy + let whereClause: Where = (\.master ~ \.$name) == dummy let predicate = NSPredicate(format: "master.name == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -265,7 +265,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.spouse ~ \.name) == dummy + let whereClause: Where = (\.master ~ \.spouse ~ \.$name) == dummy let predicate = NSPredicate(format: "master.spouse.name == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -301,7 +301,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets ~ \.species).any() == dummy + let whereClause: Where = (\.master ~ \.pets ~ \.$species).any() == dummy let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -319,7 +319,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets ~ \.species).all() == dummy + let whereClause: Where = (\.master ~ \.pets ~ \.$species).all() == dummy let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) @@ -337,7 +337,7 @@ final class WhereTests: XCTestCase { } do { - let whereClause: Where = (\.master ~ \.pets ~ \.species).none() == dummy + let whereClause: Where = (\.master ~ \.pets ~ \.$species).none() == dummy let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy) XCTAssertAllEqual(whereClause, Where(predicate)) XCTAssertAllEqual(whereClause.predicate, predicate) diff --git a/Sources/AttributeProtocol.swift b/Sources/AttributeProtocol.swift index 5447d9a..01f5fb3 100644 --- a/Sources/AttributeProtocol.swift +++ b/Sources/AttributeProtocol.swift @@ -29,7 +29,7 @@ import CoreData // MARK: - AttributeProtocol -internal protocol AttributeProtocol: PropertyProtocol { +internal protocol AttributeProtocol: AnyObject, PropertyProtocol { typealias EntityDescriptionValues = ( attributeType: NSAttributeType, diff --git a/Sources/CoreStore+CustomDebugStringConvertible.swift b/Sources/CoreStore+CustomDebugStringConvertible.swift index 4eb4144..1825acd 100644 --- a/Sources/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/CoreStore+CustomDebugStringConvertible.swift @@ -94,9 +94,10 @@ extension CoreStoreError: CustomDebugStringConvertible, CoreStoreDebugStringConv firstLine = ".progressiveMigrationRequired" info.append(("localStoreURL", localStoreURL)) - case .asynchronousMigrationRequired(let localStoreURL): + case .asynchronousMigrationRequired(let localStoreURL, let NSError): firstLine = ".asynchronousMigrationRequired" info.append(("localStoreURL", localStoreURL)) + info.append(("NSError", NSError)) case .internalError(let NSError): firstLine = ".internalError" diff --git a/Sources/CoreStoreError.swift b/Sources/CoreStoreError.swift index ad19859..e2a5dd9 100644 --- a/Sources/CoreStoreError.swift +++ b/Sources/CoreStoreError.swift @@ -198,9 +198,6 @@ public enum CoreStoreError: Error, CustomNSError, Hashable { case (.userError(let error1), .userError(let error2)): switch (error1, error2) { - - case (let error1 as AnyHashable, let error2 as AnyHashable): - return error1 == error2 case (let error1 as NSError, let error2 as NSError): return error1.isEqual(error2) diff --git a/Sources/CoreStoreObject+DataSources.swift b/Sources/CoreStoreObject+DataSources.swift index 52155f4..2a18563 100644 --- a/Sources/CoreStoreObject+DataSources.swift +++ b/Sources/CoreStoreObject+DataSources.swift @@ -1,9 +1,26 @@ // // CoreStoreObject+DataSources.swift -// CoreStore iOS +// CoreStore // -// Created by John Estropia on 2019/10/04. -// Copyright © 2019 John Rommel Estropia. All rights reserved. +// 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. // #if canImport(UIKit) || canImport(AppKit) diff --git a/Sources/CoreStoreObject+Observing.swift b/Sources/CoreStoreObject+Observing.swift index b9f4996..a04f839 100644 --- a/Sources/CoreStoreObject+Observing.swift +++ b/Sources/CoreStoreObject+Observing.swift @@ -27,6 +27,33 @@ import Foundation import CoreData +extension DynamicObject where Self: CoreStoreObject { + + public func observe(_ keyPath: KeyPath.Stored>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, CoreStoreObjectValueDiff) -> Void) -> CoreStoreObjectKeyValueObservation { + + let result = _CoreStoreObjectKeyValueObservation( + object: self.rawObject!, + keyPath: self[keyPath: keyPath].keyPath, + callback: { (object, kind, newValue, oldValue, _, isPrior) in + + let notification = CoreStoreObjectValueDiff( + kind: kind, + newNativeValue: newValue as? V.QueryableNativeType, + oldNativeValue: oldValue as? V.QueryableNativeType, + isPrior: isPrior + ) + changeHandler( + Self.cs_fromRaw(object: object), + notification + ) + } + ) + result.start(options) + return result + } +} + + // MARK: CoreStoreObjectKeyValueObservation /** diff --git a/Sources/CoreStoreObject+Querying.swift b/Sources/CoreStoreObject+Querying.swift index 8d21e91..c63b937 100644 --- a/Sources/CoreStoreObject+Querying.swift +++ b/Sources/CoreStoreObject+Querying.swift @@ -26,6 +26,88 @@ import CoreData import Foundation +// MARK: - FieldContainer.Value + +extension FieldContainer.Stored { + + /** + Creates a `Where` clause by comparing if a property is equal to a value + ``` + let person = dataStack.fetchOne(From().where({ $0.nickname == "John" })) + ``` + */ + public static func == (_ attribute: Self, _ value: V) -> Where { + + return Where(attribute.keyPath, isEqualTo: value) + } + + /** + Creates a `Where` clause by comparing if a property is not equal to a value + ``` + let person = dataStack.fetchOne(From().where({ $0.nickname != "John" })) + ``` + */ + public static func != (_ attribute: Self, _ value: V) -> Where { + + return !Where(attribute.keyPath, isEqualTo: value) + } + + /** + Creates a `Where` clause by comparing if a property is less than a value + ``` + let person = dataStack.fetchOne(From().where({ $0.age < 20 })) + ``` + */ + public static func < (_ attribute: Self, _ value: V) -> Where { + + return Where("%K < %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any) + } + + /** + Creates a `Where` clause by comparing if a property is greater than a value + ``` + let person = dataStack.fetchOne(From().where({ $0.age > 20 })) + ``` + */ + public static func > (_ attribute: Self, _ value: V) -> Where { + + return Where("%K > %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any) + } + + /** + Creates a `Where` clause by comparing if a property is less than or equal to a value + ``` + let person = dataStack.fetchOne(From().where({ $0.age <= 20 })) + ``` + */ + public static func <= (_ attribute: Self, _ value: V) -> Where { + + return Where("%K <= %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any) + } + + /** + Creates a `Where` clause by comparing if a property is greater than or equal to a value + ``` + let person = dataStack.fetchOne(From().where({ $0.age >= 20 })) + ``` + */ + public static func >= (_ attribute: Self, _ value: V) -> Where { + + return Where("%K >= %@", attribute.keyPath, value.cs_toFieldStoredNativeType() as Any) + } + + /** + Creates a `Where` clause by checking if a sequence contains the value of a property + ``` + let dog = dataStack.fetchOne(From().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname })) + ``` + */ + public static func ~= (_ sequence: S, _ attribute: Self) -> Where where S.Iterator.Element == V { + + return Where(attribute.keyPath, isMemberOf: sequence) + } +} + // MARK: - ValueContainer.Required extension ValueContainer.Required { diff --git a/Sources/CoreStoreObject.swift b/Sources/CoreStoreObject.swift index 6040d85..001a646 100644 --- a/Sources/CoreStoreObject.swift +++ b/Sources/CoreStoreObject.swift @@ -69,6 +69,11 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { self.isMeta = false self.rawObject = (rawObject as! CoreStoreManagedObject) + + guard Self.meta.needsReflection else { + + return + } self.registerReceiver( mirror: Mirror(reflecting: self), object: self @@ -117,6 +122,11 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { internal let rawObject: CoreStoreManagedObject? internal let isMeta: Bool + internal lazy var needsReflection: Bool = self.containsLegacyAttributes( + mirror: Mirror(reflecting: self), + object: self + ) + internal class func metaProperties(includeSuperclasses: Bool) -> [PropertyProtocol] { func keyPaths(_ allKeyPaths: inout [PropertyProtocol], for dynamicType: CoreStoreObject.Type) { @@ -137,9 +147,33 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable { keyPaths(&allKeyPaths, for: self) return allKeyPaths } - + // MARK: Private + + private func containsLegacyAttributes(mirror: Mirror, object: CoreStoreObject) -> Bool { + + if let superclassMirror = mirror.superclassMirror, + self.containsLegacyAttributes(mirror: superclassMirror, object: object) { + + return true + } + for child in mirror.children { + + switch child.value { + + case is AttributeProtocol: + return true + + case is RelationshipProtocol: + return true + + default: + continue + } + } + return false + } private func registerReceiver(mirror: Mirror, object: CoreStoreObject) { diff --git a/Sources/CoreStoreSchema.swift b/Sources/CoreStoreSchema.swift index e6ff4e1..39252ad 100644 --- a/Sources/CoreStoreSchema.swift +++ b/Sources/CoreStoreSchema.swift @@ -286,6 +286,25 @@ public final class CoreStoreSchema: DynamicSchema { for property in type.metaProperties(includeSuperclasses: false) { switch property { + + case let attribute as FieldAttributeProtocol: + Internals.assert( + !NSManagedObject.instancesRespond(to: Selector(attribute.keyPath)), + "Attribute Property name \"\(String(reflecting: entity.type)).\(attribute.keyPath)\" is not allowed because it collides with \"\(String(reflecting: NSManagedObject.self)).\(attribute.keyPath)\"" + ) + let entityDescriptionValues = attribute.entityDescriptionValues() + let description = NSAttributeDescription() + description.name = attribute.keyPath + description.attributeType = entityDescriptionValues.attributeType + description.isOptional = entityDescriptionValues.isOptional + description.defaultValue = entityDescriptionValues.defaultValue + description.isTransient = entityDescriptionValues.isTransient + description.allowsExternalBinaryDataStorage = entityDescriptionValues.allowsExternalBinaryDataStorage + description.versionHashModifier = entityDescriptionValues.versionHashModifier + description.renamingIdentifier = entityDescriptionValues.renamingIdentifier + propertyDescriptions.append(description) + keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths + customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter) case let attribute as AttributeProtocol: Internals.assert( diff --git a/Sources/DynamicObject.swift b/Sources/DynamicObject.swift index f3fb9ef..101d580 100644 --- a/Sources/DynamicObject.swift +++ b/Sources/DynamicObject.swift @@ -153,41 +153,68 @@ extension CoreStoreObject { public class func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any]? { - func initializeAttributes(mirror: Mirror, object: Self, into attributes: inout [KeyPathString: Any]) { + var values: [KeyPathString: Any] = [:] + if self.meta.needsReflection { - if let superClassMirror = mirror.superclassMirror { + func initializeAttributes(mirror: Mirror, object: Self, into attributes: inout [KeyPathString: Any]) { - initializeAttributes( - mirror: superClassMirror, - object: object, - into: &attributes - ) + if let superClassMirror = mirror.superclassMirror { + + initializeAttributes( + mirror: superClassMirror, + object: object, + into: &attributes + ) + } + for child in mirror.children { + + switch child.value { + + case let property as FieldAttributeProtocol: + attributes[property.keyPath] = type(of: property).read(field: property, for: object.rawObject!) + + case let property as AttributeProtocol: + attributes[property.keyPath] = property.valueForSnapshot + + case let property as RelationshipProtocol: + attributes[property.keyPath] = property.valueForSnapshot + + default: + continue + } + } } - for child in mirror.children { + guard let object = context.fetchExisting(id) as CoreStoreObject? else { - switch child.value { + return nil + } + initializeAttributes( + mirror: Mirror(reflecting: object), + object: object as! Self, + into: &values + ) + } + else { - case let property as AttributeProtocol: - attributes[property.keyPath] = property.valueForSnapshot + guard + let object = context.fetchExisting(id) as CoreStoreObject?, + let rawObject = object.rawObject + else { - case let property as RelationshipProtocol: - attributes[property.keyPath] = property.valueForSnapshot + return nil + } + for property in self.metaProperties(includeSuperclasses: true) { + + switch property { + + case let property as FieldAttributeProtocol: + values[property.keyPath] = type(of: property).read(field: property, for: rawObject) default: continue } } } - guard let object = context.fetchExisting(id) as CoreStoreObject? else { - - return nil - } - var values: [KeyPathString: Any] = [:] - initializeAttributes( - mirror: Mirror(reflecting: object), - object: object as! Self, - into: &values - ) return values } diff --git a/Sources/DynamicObjectMeta.swift b/Sources/DynamicObjectMeta.swift deleted file mode 100644 index dec8602..0000000 --- a/Sources/DynamicObjectMeta.swift +++ /dev/null @@ -1,143 +0,0 @@ -// -// DynamicObjectMeta.swift -// CoreStore iOS -// -// Created by John Estropia on 2019/08/20. -// Copyright © 2019 John Rommel Estropia. All rights reserved. -// - -#if swift(>=5.1) - -import CoreData -import Foundation - - -// MARK: - DynamicObjectMeta - -@dynamicMemberLookup -public struct DynamicObjectMeta: CustomDebugStringConvertible { - - // MARK: Public - - public typealias Root = R - public typealias Destination = D - - - // MARK: CustomDebugStringConvertible - - public var debugDescription: String { - - return self.keyPathString - } - - - // MARK: Internal - - internal let keyPathString: KeyPathString - - internal init(keyPathString: KeyPathString) { - - self.keyPathString = keyPathString - } - - internal func appending(keyPathString: KeyPathString) -> DynamicObjectMeta<(R, D), D2> { - - return .init(keyPathString: [self.keyPathString, keyPathString].joined(separator: ".")) - } -} - - -// MARK: - DynamicObjectMeta where Destination: NSManagedObject - -extension DynamicObjectMeta where Destination: NSManagedObject { - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V.ReturnValueType> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V> { - - // TODO: not working - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), V> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } -} - - -// MARK: - DynamicObjectMeta where Destination: CoreStoreObject - -extension DynamicObjectMeta where Destination: CoreStoreObject { - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), K.ReturnValueType> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } - - /** - Returns the value for the property identified by a given key. - */ - public subscript(dynamicMember member: KeyPath) -> DynamicObjectMeta<(Root, Destination), K.DestinationValueType> { - - let keyPathString = String(keyPath: member) - return self.appending(keyPathString: keyPathString) - } -} - -#endif diff --git a/Sources/Field.Computed.swift b/Sources/Field.Computed.swift new file mode 100644 index 0000000..1bd55dd --- /dev/null +++ b/Sources/Field.Computed.swift @@ -0,0 +1,305 @@ +// +// Field.Computed.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: - Computed + + /** + The containing type for computed 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.Computed(...) var` syntax will be ignored. + */ + @propertyWrapper + public struct Computed: 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 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 = []) { + + self.init( + keyPath: keyPath, + isOptional: false, + renamingIdentifier: renamingIdentifier, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } + + /** + Overload for compiler message only + */ + @available(*, unavailable, message: "Field.Computed properties are not allowed to have default values.") + 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 = []) { + + fatalError() + } + + + // 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? { + + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + return customGetter(PartialObject(rawObject)) + } + } + + 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) + } + return customSetter(PartialObject(rawObject), newValue as! V) + } + } + + + // MARK: FilePrivate + + fileprivate init( + keyPath: KeyPathString, + isOptional: Bool, + renamingIdentifier: @escaping () -> String?, + customGetter: ((_ partialObject: PartialObject) -> V)?, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? , + affectedByKeyPaths: @escaping () -> Set) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + attributeType: .undefinedAttributeType, + isOptional: isOptional, + isTransient: true, + allowsExternalBinaryDataStorage: false, + versionHashModifier: nil, + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: nil + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? + } +} diff --git a/Sources/Field.PlistCoded.swift b/Sources/Field.PlistCoded.swift new file mode 100644 index 0000000..192ae0d --- /dev/null +++ b/Sources/Field.PlistCoded.swift @@ -0,0 +1,9 @@ +// +// 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 new file mode 100644 index 0000000..d9a7b0a --- /dev/null +++ b/Sources/Field.Stored.swift @@ -0,0 +1,362 @@ +// +// Field.Stored.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: - Stored + + /** + 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 Stored: 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 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( + 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 = []) { + + self.init( + wrappedValue: initial, + keyPath: keyPath, + isOptional: false, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + 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 + guard case let rawValue as V.FieldStoredNativeType = rawObject.value(forKey: keyPath) else { + + return nil + } + return V.cs_fromFieldStoredNativeType(rawValue) + } + + 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.cs_toFieldStoredNativeType(), + forKey: keyPath + ) + } + + + // MARK: FieldAttributeProtocol + + internal let entityDescriptionValues: () -> FieldAttributeProtocol.EntityDescriptionValues + + internal var getter: CoreStoreManagedObject.CustomGetter? { + + guard let customGetter = self.customGetter else { + + return nil + } + let keyPath = self.keyPath + return { (_ id: Any) -> Any? in + + let rawObject = id as! CoreStoreManagedObject + rawObject.willAccessValue(forKey: keyPath) + defer { + + rawObject.didAccessValue(forKey: keyPath) + } + let value = customGetter(PartialObject(rawObject)) + return value.cs_toFieldStoredNativeType() + } + } + + 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), + V.cs_fromFieldStoredNativeType(newValue as! V.FieldStoredNativeType) + ) + } + } + + + // MARK: FilePrivate + + fileprivate init( + wrappedValue initial: @escaping () -> V, + keyPath: KeyPathString, + isOptional: Bool, + versionHashModifier: @escaping () -> String?, + renamingIdentifier: @escaping () -> String?, + customGetter: ((_ partialObject: PartialObject) -> V)?, + customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? , + affectedByKeyPaths: @escaping () -> Set) { + + self.keyPath = keyPath + self.entityDescriptionValues = { + ( + attributeType: V.cs_rawAttributeType, + isOptional: isOptional, + isTransient: false, + allowsExternalBinaryDataStorage: false, + versionHashModifier: versionHashModifier(), + renamingIdentifier: renamingIdentifier(), + affectedByKeyPaths: affectedByKeyPaths(), + defaultValue: initial().cs_toFieldStoredNativeType() + ) + } + self.customGetter = customGetter + self.customSetter = customSetter + } + + + // MARK: Private + + private let customGetter: ((_ partialObject: PartialObject) -> V)? + private let customSetter: ((_ partialObject: PartialObject, _ newValue: V) -> Void)? + } +} + + +// MARK: - FieldContainer.Stored where V: FieldOptionalType + +extension FieldContainer.Stored 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 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( + wrappedValue initial: @autoclosure @escaping () -> V = nil, + _ 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 = []) { + + self.init( + wrappedValue: initial, + keyPath: keyPath, + isOptional: true, + versionHashModifier: versionHashModifier, + renamingIdentifier: renamingIdentifier, + customGetter: customGetter, + customSetter: customSetter, + affectedByKeyPaths: affectedByKeyPaths + ) + } +} diff --git a/Sources/Field.swift b/Sources/Field.swift new file mode 100644 index 0000000..19f4df6 --- /dev/null +++ b/Sources/Field.swift @@ -0,0 +1,71 @@ +// +// Field.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: - DynamicObject + +extension DynamicObject where Self: CoreStoreObject { + + /** + The containing type for value propertiess. `Field` properties support any type that conforms to `ImportableAttributeType`. + ``` + class Animal: CoreStoreObject { + @Field.Stored("species") + var species = "" + + @Field.Stored("nickname") + var nickname: 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.*(...) var` syntax will be ignored. + */ + public typealias Field = FieldContainer +} + + +// MARK: - FieldContainer + +/** + The containing type for value properties. Use the `DynamicObject.Field` typealias instead for shorter syntax. + ``` + class Animal: CoreStoreObject { + @Field.Stored("species") + var species = "" + + @Field.Stored("nickname") + var nickname: String? + + @Field.PlistCoded("color") + var color: UIColor? + } + ``` + */ +public enum FieldContainer {} diff --git a/Sources/FieldOptionalType.swift b/Sources/FieldOptionalType.swift new file mode 100644 index 0000000..ae7b80b --- /dev/null +++ b/Sources/FieldOptionalType.swift @@ -0,0 +1,56 @@ +// +// FieldOptionalType.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: - FieldOptionalType + +public protocol FieldOptionalType: ExpressibleByNilLiteral { + + /** + The type for the wrapped value + */ + associatedtype Wrapped + + /** + The wrapped value + */ + var cs_wrappedValue: Wrapped? { get } +} + + +// MARK: - Optional: FieldOptionalType + +extension Optional: FieldOptionalType { + + // MARK: Public + + @inlinable + public var cs_wrappedValue: Wrapped? { + + return self + } +} diff --git a/Sources/FieldProtocol.swift b/Sources/FieldProtocol.swift new file mode 100644 index 0000000..e38e642 --- /dev/null +++ b/Sources/FieldProtocol.swift @@ -0,0 +1,57 @@ +// +// FieldProtocol.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: - FieldProtocol + +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/FieldStorableType.swift b/Sources/FieldStorableType.swift new file mode 100644 index 0000000..5428889 --- /dev/null +++ b/Sources/FieldStorableType.swift @@ -0,0 +1,259 @@ +// +// FieldStorableType.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 +import CoreGraphics + + +// MARK: - FieldStorableType + +public protocol FieldStorableType { + + /** + The `NSAttributeType` for this type + */ + associatedtype FieldStoredNativeType + + /** + The `NSAttributeType` for this type + */ + static var cs_rawAttributeType: NSAttributeType { get } + + /** + Creates an instance of this type from raw native value. + */ + @inline(__always) + static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self + + /** + Creates `FieldStoredNativeType` value from this instance. + */ + @inline(__always) + func cs_toFieldStoredNativeType() -> Any? +} + + +// MARK: - FieldStorableType where Self: ImportableAttributeType, FieldStoredNativeType == QueryableNativeType + +extension FieldStorableType where Self: ImportableAttributeType, FieldStoredNativeType == QueryableNativeType { + + @inline(__always) + public static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } + + @inline(__always) + public func cs_toFieldStoredNativeType() -> Any? { + + return self.cs_toQueryableNativeType() + } +} + + +// MARK: - Bool + +extension Bool: FieldStorableType {} + + +// MARK: - CGFloat + +extension CGFloat: FieldStorableType {} + + +// MARK: - Data + +extension Data: FieldStorableType {} + + +// MARK: - Date + +extension Date: FieldStorableType {} + + +// MARK: - Double + +extension Double: FieldStorableType {} + + +// MARK: - Float + +extension Float: FieldStorableType {} + + +// MARK: - Int + +extension Int: FieldStorableType {} + + +// MARK: - Int8 + +extension Int8: FieldStorableType {} + + +// MARK: - Int16 + +extension Int16: FieldStorableType {} + + +// MARK: - Int32 + +extension Int32: FieldStorableType {} + + +// MARK: - Int64 + +extension Int64: FieldStorableType {} + + +// MARK: - NSData + +extension NSData: FieldStorableType { + + @nonobjc @inline(__always) + public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } +} + + +// MARK: - NSDate + +extension NSDate: FieldStorableType { + + @nonobjc @inline(__always) + public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } +} + + +// MARK: - NSNumber + +extension NSNumber: FieldStorableType { + + @nonobjc @inline(__always) + public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } +} + + +// MARK: - NSString + +extension NSString: FieldStorableType { + + @nonobjc @inline(__always) + public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } +} + + +// MARK: - NSURL + +extension NSURL: FieldStorableType { + + @nonobjc @inline(__always) + public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } +} + + +// MARK: - NSUUID + +extension NSUUID: FieldStorableType { + + @nonobjc @inline(__always) + public class func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + return self.cs_fromQueryableNativeType(value)! + } +} + + +// MARK: - String + +extension String: FieldStorableType {} + + +// MARK: - URL + +extension URL: FieldStorableType {} + + +// MARK: - UUID + +extension UUID: FieldStorableType {} + + +// MARK: - Optional + +extension Optional: FieldStorableType where Wrapped: FieldStorableType { + + // MARK: FieldStorableType + + public typealias FieldStoredNativeType = Wrapped.FieldStoredNativeType? + + public static var cs_rawAttributeType: NSAttributeType { + + return Wrapped.cs_rawAttributeType + } + + @inline(__always) + public static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self { + + switch value { + + case nil, + is NSNull: + return nil + + case let value?: + return Wrapped.cs_fromFieldStoredNativeType(value) + } + } + + @inline(__always) + public func cs_toFieldStoredNativeType() -> Any? { + + switch self { + + case nil, + is NSNull: + return nil + + case let value?: + return value.cs_toFieldStoredNativeType() + } + } +} diff --git a/Sources/KeyPath+Querying.swift b/Sources/KeyPath+Querying.swift index f83a8d5..333ae49 100644 --- a/Sources/KeyPath+Querying.swift +++ b/Sources/KeyPath+Querying.swift @@ -359,6 +359,20 @@ public func ~= (_ sequence: } +// MARK: - KeyPath where Root: CoreStoreObject, Value: FieldContainer.Stored + +/** + Creates a `Where` clause by comparing if a property is equal to a value + ``` + let person = dataStack.fetchOne(From().where(\.nickname == "John")) + ``` + */ +public func == (_ keyPath: KeyPath.Stored>, _ value: V) -> Where { + + return Where(keyPath, isEqualTo: value) +} + + // MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer.Required /** diff --git a/Sources/KeyPathGenericBindings.swift b/Sources/KeyPathGenericBindings.swift index 0439a34..a7cd483 100644 --- a/Sources/KeyPathGenericBindings.swift +++ b/Sources/KeyPathGenericBindings.swift @@ -106,47 +106,47 @@ extension Int64: AllowedObjectiveCKeyPathValue { extension NSData: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSData } extension NSDate: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSDate } extension NSManagedObject: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSManagedObject } extension NSNumber: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSNumber } extension NSString: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSString } extension NSSet: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSSet } extension NSOrderedSet: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSOrderedSet } extension NSURL: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSURL } extension NSUUID: AllowedOptionalObjectiveCKeyPathValue { - public typealias DestinationValueType = Self + public typealias DestinationValueType = NSUUID } extension String: AllowedOptionalObjectiveCKeyPathValue { @@ -240,32 +240,32 @@ extension Int64: AllowedObjectiveCAttributeKeyPathValue { extension NSData: AllowedObjectiveCAttributeKeyPathValue { - public typealias ReturnValueType = Self + public typealias ReturnValueType = NSData } extension NSDate: AllowedObjectiveCAttributeKeyPathValue { - public typealias ReturnValueType = Self + public typealias ReturnValueType = NSDate } extension NSNumber: AllowedObjectiveCAttributeKeyPathValue { - public typealias ReturnValueType = Self + public typealias ReturnValueType = NSNumber } extension NSString: AllowedObjectiveCAttributeKeyPathValue { - public typealias ReturnValueType = Self + public typealias ReturnValueType = NSString } extension NSURL: AllowedObjectiveCAttributeKeyPathValue { - public typealias ReturnValueType = Self + public typealias ReturnValueType = NSURL } extension NSUUID: AllowedObjectiveCAttributeKeyPathValue { - public typealias ReturnValueType = Self + public typealias ReturnValueType = NSUUID } extension String: AllowedObjectiveCAttributeKeyPathValue { diff --git a/Sources/ObjectPublisher.swift b/Sources/ObjectPublisher.swift index d2ff520..5426925 100644 --- a/Sources/ObjectPublisher.swift +++ b/Sources/ObjectPublisher.swift @@ -367,6 +367,36 @@ extension ObjectPublisher where O: CoreStoreObject { // MARK: Public + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Stored>) -> V? { + + guard + let object = self.object, + let rawObject = object.rawObject + else { + + return nil + } + return FieldContainer.Stored.read(field: object[keyPath: member], for: rawObject) as! V? + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Computed>) -> V? { + + guard + let object = self.object, + let rawObject = object.rawObject + else { + + return nil + } + return FieldContainer.Computed.read(field: object[keyPath: member], for: rawObject) as! V? + } + /** Returns the value for the property identified by a given key. */ diff --git a/Sources/ObjectSnapshot.swift b/Sources/ObjectSnapshot.swift index 9eb5a9f..0845636 100644 --- a/Sources/ObjectSnapshot.swift +++ b/Sources/ObjectSnapshot.swift @@ -175,6 +175,40 @@ extension ObjectSnapshot where O: NSManagedObject { extension ObjectSnapshot where O: CoreStoreObject { + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Stored>) -> V { + + get { + + let key = String(keyPath: member) + return self.values[key] as! V + } + set { + + let key = String(keyPath: member) + self.values[key] = newValue + } + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Computed>) -> V { + + get { + + let key = String(keyPath: member) + return self.values[key] as! V + } + set { + + let key = String(keyPath: member) + self.values[key] = newValue + } + } + /** Returns the value for the property identified by a given key. */ diff --git a/Sources/PartialObject.swift b/Sources/PartialObject.swift index 9d42664..be8c345 100644 --- a/Sources/PartialObject.swift +++ b/Sources/PartialObject.swift @@ -41,6 +41,89 @@ public struct PartialObject { return O.cs_fromRaw(object: self.rawObject) } + + + // MARK: Field.Stored accessors/mutators + + /** + Returns the value for the property identified by a given key. + */ + public func value(for property: (O) -> FieldContainer.Stored) -> V { + + return V.cs_fromFieldStoredNativeType( + self.rawObject.value(forKey: property(O.meta).keyPath) as! V.FieldStoredNativeType + ) + } + + /** + Returns the value for the property identified by a given key. + */ + public func value(for property: (O) -> FieldContainer.Computed) -> V { + + switch self.rawObject.value(forKey: property(O.meta).keyPath) { + + case let value as V: + return value + + default: + return nil as Any? as! V // filter NSNull + } + } + + /** + 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.Stored) -> V { + + return V.cs_fromFieldStoredNativeType( + self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V.FieldStoredNativeType + ) + } + + /** + 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.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 + } + } + + /** + Sets in the object's private internal storage the value of a given property. + + Sets in the receiver’s private internal storage the value of the property specified by key to value. + */ + public func setPrimitiveValue(_ value: V, for property: (O) -> FieldContainer.Stored) { + + self.rawObject.setPrimitiveValue( + value.cs_toFieldStoredNativeType(), + forKey: property(O.meta).keyPath + ) + } + + /** + Sets in the object's private internal storage the value of a given property. + + Sets in the receiver’s private internal storage the value of the property specified by key to value. + */ + public func setPrimitiveValue(_ value: V, for property: (O) -> FieldContainer.Computed) { + + self.rawObject.setPrimitiveValue( + value, + forKey: property(O.meta).keyPath + ) + } // MARK: Value.Required accessors/mutators diff --git a/Sources/PropertyProtocol.swift b/Sources/PropertyProtocol.swift index 1c6292a..70c2551 100644 --- a/Sources/PropertyProtocol.swift +++ b/Sources/PropertyProtocol.swift @@ -29,7 +29,7 @@ import CoreData // MARK: - PropertyProtocol -internal protocol PropertyProtocol: AnyObject { +internal protocol PropertyProtocol { var keyPath: KeyPathString { get } } diff --git a/Sources/RelationshipProtocol.swift b/Sources/RelationshipProtocol.swift index cd0b4c6..78a637f 100644 --- a/Sources/RelationshipProtocol.swift +++ b/Sources/RelationshipProtocol.swift @@ -29,7 +29,7 @@ import CoreData // MARK: - RelationshipProtocol -internal protocol RelationshipProtocol: PropertyProtocol { +internal protocol RelationshipProtocol: AnyObject, PropertyProtocol { typealias EntityDescriptionValues = ( isToMany: Bool, diff --git a/Sources/StorageInterface.swift b/Sources/StorageInterface.swift index 3eb8581..a94a49f 100644 --- a/Sources/StorageInterface.swift +++ b/Sources/StorageInterface.swift @@ -73,22 +73,22 @@ public struct LocalStorageOptions: OptionSet, ExpressibleByNilLiteral { /** Tells the `DataStack` that the store should not be migrated or recreated, and should simply fail on model mismatch */ - public static let none = LocalStorageOptions(rawValue: 0) + public static let none: LocalStorageOptions = [] /** Tells the `DataStack` to delete and recreate the store on model mismatch, otherwise exceptions will be thrown on failure instead */ - public static let recreateStoreOnModelMismatch = LocalStorageOptions(rawValue: 1 << 0) + public static let recreateStoreOnModelMismatch: LocalStorageOptions = .init(rawValue: 1 << 0) /** Tells the `DataStack` to prevent progressive migrations for the store */ - public static let preventProgressiveMigration = LocalStorageOptions(rawValue: 1 << 1) + public static let preventProgressiveMigration: LocalStorageOptions = .init(rawValue: 1 << 1) /** Tells the `DataStack` to allow lightweight migration for the store when added synchronously */ - public static let allowSynchronousLightweightMigration = LocalStorageOptions(rawValue: 1 << 2) + public static let allowSynchronousLightweightMigration: LocalStorageOptions = .init(rawValue: 1 << 2) diff --git a/Sources/VersionLock.swift b/Sources/VersionLock.swift index 5e5d705..fa4fd3d 100644 --- a/Sources/VersionLock.swift +++ b/Sources/VersionLock.swift @@ -110,13 +110,8 @@ public struct VersionLock: ExpressibleByDictionaryLiteral, Equatable { var hashesByEntityName: [EntityName: Data] = [:] for (entityName, intArray) in keyValues { - - hashesByEntityName[entityName] = Data( - buffer: UnsafeBufferPointer( - start: intArray, - count: intArray.count - ) - ) + + hashesByEntityName[entityName] = intArray.withUnsafeBufferPointer(Data.init(buffer:)) } self.hashesByEntityName = hashesByEntityName } diff --git a/Sources/Where.swift b/Sources/Where.swift index 9c01106..f6b3af7 100644 --- a/Sources/Where.swift +++ b/Sources/Where.swift @@ -174,21 +174,41 @@ public struct Where: WhereClauseType, FetchClause, QueryClause self.init(NSPredicate(format: "\(keyPath) == nil")) } - + /** Initializes a `Where` clause that compares equality - + - parameter keyPath: the keyPath to compare with - parameter value: the arguments for the `==` operator */ - public init(_ keyPath: KeyPathString, isEqualTo value: U?) { - + public init(_ keyPath: KeyPathString, isEqualTo value: V) { + switch value { - + case nil, is NSNull: self.init(NSPredicate(format: "\(keyPath) == nil")) - + + case let value: + self.init(NSPredicate(format: "\(keyPath) == %@", argumentArray: [value.cs_toFieldStoredNativeType() as Any])) + } + } + + /** + Initializes a `Where` clause that compares equality + + - parameter keyPath: the keyPath to compare with + - parameter value: the arguments for the `==` operator + */ + @_disfavoredOverload + public init(_ keyPath: KeyPathString, isEqualTo value: U?) { + + switch value { + + case nil, + is NSNull: + self.init(NSPredicate(format: "\(keyPath) == nil")) + case let value?: self.init(NSPredicate(format: "\(keyPath) == %@", argumentArray: [value.cs_toQueryableNativeType()])) } @@ -222,6 +242,17 @@ public struct Where: WhereClauseType, FetchClause, QueryClause self.init(NSPredicate(format: "\(keyPath) == %@", argumentArray: [objectID])) } + + /** + Initializes a `Where` clause that compares membership + + - parameter keyPath: the keyPath to compare with + - parameter list: the sequence to check membership of + */ + public init(_ keyPath: KeyPathString, isMemberOf list: S) where S.Iterator.Element: FieldStorableType { + + self.init(NSPredicate(format: "\(keyPath) IN %@", list.map({ $0.cs_toFieldStoredNativeType() }) as NSArray)) + } /** Initializes a `Where` clause that compares membership @@ -229,6 +260,7 @@ public struct Where: WhereClauseType, FetchClause, QueryClause - parameter keyPath: the keyPath to compare with - parameter list: the sequence to check membership of */ + @_disfavoredOverload public init(_ keyPath: KeyPathString, isMemberOf list: S) where S.Iterator.Element: QueryableAttributeType { self.init(NSPredicate(format: "\(keyPath) IN %@", list.map({ $0.cs_toQueryableNativeType() }) as NSArray)) @@ -408,6 +440,17 @@ extension Where where O: NSManagedObject { // MARK: - Where where O: CoreStoreObject extension Where where O: CoreStoreObject { + + /** + Initializes a `Where` clause that compares equality + + - parameter keyPath: the keyPath to compare with + - parameter value: the arguments for the `==` operator + */ + public init(_ keyPath: KeyPath.Stored>, isEqualTo value: V) { + + self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value) + } /** Initializes a `Where` clause that compares equality to `nil`