mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-13 23:23:29 +01:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73b7fcd907 | ||
|
|
74c1a97af4 | ||
|
|
97f2a53124 | ||
|
|
b6db872be0 | ||
|
|
0d9299f900 | ||
|
|
7f928dc684 | ||
|
|
231e138ab0 | ||
|
|
361dba58c6 | ||
|
|
e1b03b4a89 | ||
|
|
0df6c737c1 | ||
|
|
12c5aeaaa4 | ||
|
|
dd3fb17dd0 | ||
|
|
627a5d4355 | ||
|
|
58629bc1df | ||
|
|
f925803b93 | ||
|
|
843adf21f7 | ||
|
|
2d1b1e0592 | ||
|
|
38e9878c04 | ||
|
|
8cb8b95c2e | ||
|
|
8e4e308ccc | ||
|
|
f0f4049798 | ||
|
|
c20fe4ac17 | ||
|
|
e9c3312612 | ||
|
|
92ad895044 | ||
|
|
bcc2d9def3 | ||
|
|
43f61359da | ||
|
|
5e37ee4566 | ||
|
|
c544e0cce8 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [JohnEstropia]
|
||||
@@ -1,7 +1,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "7.0.3"
|
||||
s.swift_version = "5.1"
|
||||
s.version = "7.1.0"
|
||||
s.swift_version = "5.2"
|
||||
s.license = "MIT"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
s.documentation_url = "https://JohnEstropia.github.io/CoreStore"
|
||||
|
||||
@@ -105,6 +105,70 @@
|
||||
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
|
||||
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
|
||||
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
|
||||
B509D7BA23C846E300F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
|
||||
B509D7BC23C847BC00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
|
||||
B509D7BD23C8480A00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
|
||||
B509D7BE23C8480A00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
|
||||
B509D7BF23C8480B00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
|
||||
B509D7C023C8480B00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
|
||||
B509D7C123C8480B00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
|
||||
B509D7C223C8480B00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
|
||||
B509D7C423C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
|
||||
B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
|
||||
B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
|
||||
B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
|
||||
B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
|
||||
B509D7CA23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
|
||||
B509D7CB23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
|
||||
B509D7CC23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
|
||||
B509D7CE23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
|
||||
B509D7CF23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
|
||||
B509D7D023C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
|
||||
B509D7D123C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
|
||||
B509D7D323C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
|
||||
B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
|
||||
B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
|
||||
B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
|
||||
B509D7D823C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
|
||||
B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
|
||||
B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
|
||||
B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
|
||||
B50C3EDA23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; };
|
||||
B50C3EDB23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; };
|
||||
B50C3EDC23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; };
|
||||
B50C3EDD23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */; };
|
||||
B50C3EE023D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; };
|
||||
B50C3EE123D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; };
|
||||
B50C3EE223D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; };
|
||||
B50C3EE323D062C300B29880 /* FieldCoderType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EDF23D062C300B29880 /* FieldCoderType.swift */; };
|
||||
B50C3EE523D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; };
|
||||
B50C3EE623D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; };
|
||||
B50C3EE723D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; };
|
||||
B50C3EE823D153EA00B29880 /* Field.Coded.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE423D153EA00B29880 /* Field.Coded.swift */; };
|
||||
B50C3EEA23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; };
|
||||
B50C3EEB23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; };
|
||||
B50C3EEC23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; };
|
||||
B50C3EED23D1601400B29880 /* FieldCoders.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EE923D1601400B29880 /* FieldCoders.swift */; };
|
||||
B50C3EEF23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; };
|
||||
B50C3EF023D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; };
|
||||
B50C3EF123D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; };
|
||||
B50C3EF223D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */; };
|
||||
B50C3EF423D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; };
|
||||
B50C3EF523D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; };
|
||||
B50C3EF623D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; };
|
||||
B50C3EF723D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */; };
|
||||
B50C3EF923D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; };
|
||||
B50C3EFA23D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; };
|
||||
B50C3EFB23D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; };
|
||||
B50C3EFC23D1987D00B29880 /* FieldCoders.Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */; };
|
||||
B50C3EFE23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; };
|
||||
B50C3EFF23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; };
|
||||
B50C3F0023D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; };
|
||||
B50C3F0123D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */; };
|
||||
B50C3F0323D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; };
|
||||
B50C3F0423D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; };
|
||||
B50C3F0523D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; };
|
||||
B50C3F0623D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */; };
|
||||
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
|
||||
@@ -125,6 +189,10 @@
|
||||
B50E17622351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50E17632351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50E17642351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
|
||||
B50E42F723FBB91800ED476E /* ObjectProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E42F623FBB91800ED476E /* ObjectProxy.swift */; };
|
||||
B50E42F823FBB91800ED476E /* ObjectProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E42F623FBB91800ED476E /* ObjectProxy.swift */; };
|
||||
B50E42F923FBB91800ED476E /* ObjectProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E42F623FBB91800ED476E /* ObjectProxy.swift */; };
|
||||
B50E42FA23FBB91800ED476E /* ObjectProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E42F623FBB91800ED476E /* ObjectProxy.swift */; };
|
||||
B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
|
||||
B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
|
||||
B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
|
||||
@@ -294,7 +362,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 */; };
|
||||
@@ -520,12 +587,48 @@
|
||||
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.Virtual.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Virtual.swift */; };
|
||||
B56E4EE523CEDF0900E1708C /* Field.Virtual.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Virtual.swift */; };
|
||||
B56E4EE623CEDF0900E1708C /* Field.Virtual.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Virtual.swift */; };
|
||||
B56E4EE723CEDF0900E1708C /* Field.Virtual.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E4EE323CEDF0900E1708C /* Field.Virtual.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 */; };
|
||||
B57D27C21D0BC20100539C58 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27C11D0BC20100539C58 /* QueryTests.swift */; };
|
||||
B57D27C31D0BC20100539C58 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27C11D0BC20100539C58 /* QueryTests.swift */; };
|
||||
B57D27C41D0BC20100539C58 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D27C11D0BC20100539C58 /* QueryTests.swift */; };
|
||||
B57E6FA223D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; };
|
||||
B57E6FA323D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; };
|
||||
B57E6FA423D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; };
|
||||
B57E6FA523D302FA000FD031 /* Field.Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA123D302FA000FD031 /* Field.Relationship.swift */; };
|
||||
B57E6FA723D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; };
|
||||
B57E6FA823D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; };
|
||||
B57E6FA923D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; };
|
||||
B57E6FAA23D305D6000FD031 /* FIeldRelationshipType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */; };
|
||||
B57E6FAC23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; };
|
||||
B57E6FAD23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; };
|
||||
B57E6FAE23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; };
|
||||
B57E6FAF23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */; };
|
||||
B580857A1CDF808C004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
|
||||
B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
|
||||
B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
|
||||
@@ -905,11 +1008,28 @@
|
||||
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; };
|
||||
B50564D22350CC3100482308 /* PropertyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyProtocol.swift; sourceTree = "<group>"; };
|
||||
B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableAttributeType.swift; sourceTree = "<group>"; };
|
||||
B509D7B923C846E300F42824 /* Value.Required.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Value.Required.swift; sourceTree = "<group>"; };
|
||||
B509D7BB23C847BC00F42824 /* Value.Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Value.Optional.swift; sourceTree = "<group>"; };
|
||||
B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.ToOne.swift; sourceTree = "<group>"; };
|
||||
B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.ToManyOrdered.swift; sourceTree = "<group>"; };
|
||||
B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.ToManyUnordered.swift; sourceTree = "<group>"; };
|
||||
B509D7D223C84E1900F42824 /* Transformable.Required.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.Required.swift; sourceTree = "<group>"; };
|
||||
B509D7D723C84E2600F42824 /* Transformable.Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.Optional.swift; sourceTree = "<group>"; };
|
||||
B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldAttributeProtocol.swift; sourceTree = "<group>"; };
|
||||
B50C3EDF23D062C300B29880 /* FieldCoderType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoderType.swift; sourceTree = "<group>"; };
|
||||
B50C3EE423D153EA00B29880 /* Field.Coded.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Coded.swift; sourceTree = "<group>"; };
|
||||
B50C3EE923D1601400B29880 /* FieldCoders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.swift; sourceTree = "<group>"; };
|
||||
B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.DefaultNSSecureCoding.swift; sourceTree = "<group>"; };
|
||||
B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.NSCoding.swift; sourceTree = "<group>"; };
|
||||
B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.Json.swift; sourceTree = "<group>"; };
|
||||
B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldCoders.Plist.swift; sourceTree = "<group>"; };
|
||||
B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.AnyFieldCoder.swift; sourceTree = "<group>"; };
|
||||
B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.StagedChangeset.swift; sourceTree = "<group>"; };
|
||||
B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.Changeset.swift; sourceTree = "<group>"; };
|
||||
B50E175623517DE4004F033C /* Differentiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Differentiable.swift; sourceTree = "<group>"; };
|
||||
B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.DiffResult.swift; sourceTree = "<group>"; };
|
||||
B50E17602351FA66004F033C /* Internals.Closure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.Closure.swift; sourceTree = "<group>"; };
|
||||
B50E42F623FBB91800ED476E /* ObjectProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectProxy.swift; sourceTree = "<group>"; };
|
||||
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+DataSources.swift"; sourceTree = "<group>"; };
|
||||
B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Convenience.swift"; sourceTree = "<group>"; };
|
||||
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+DynamicModel.swift"; sourceTree = "<group>"; };
|
||||
@@ -944,7 +1064,6 @@
|
||||
B52F743C1E9B8724005F3DAC /* XcodeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XcodeDataModelSchema.swift; sourceTree = "<group>"; };
|
||||
B52F74491E9B8740005F3DAC /* CoreStoreSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreSchema.swift; sourceTree = "<group>"; };
|
||||
B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Logging.swift"; sourceTree = "<group>"; };
|
||||
B53304A9230BA4F7007C2BD8 /* DynamicObjectMeta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicObjectMeta.swift; sourceTree = "<group>"; };
|
||||
B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+CoreStore.swift"; sourceTree = "<group>"; };
|
||||
B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = "<group>"; };
|
||||
B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreManagedObject.swift; sourceTree = "<group>"; };
|
||||
@@ -1003,8 +1122,17 @@
|
||||
B56923FE1EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSUnsafeDataModelSchema.swift; sourceTree = "<group>"; };
|
||||
B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "DataStack+Migration.swift"; sourceTree = "<group>"; };
|
||||
B56965231B356B820075EE4A /* MigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationResult.swift; sourceTree = "<group>"; };
|
||||
B56E4EC923CD9B4800E1708C /* Field.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.swift; sourceTree = "<group>"; };
|
||||
B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Stored.swift; sourceTree = "<group>"; };
|
||||
B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldProtocol.swift; sourceTree = "<group>"; };
|
||||
B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldStorableType.swift; sourceTree = "<group>"; };
|
||||
B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldOptionalType.swift; sourceTree = "<group>"; };
|
||||
B56E4EE323CEDF0900E1708C /* Field.Virtual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Virtual.swift; sourceTree = "<group>"; };
|
||||
B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = "<group>"; };
|
||||
B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = "<group>"; };
|
||||
B57E6FA123D302FA000FD031 /* Field.Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Field.Relationship.swift; sourceTree = "<group>"; };
|
||||
B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FIeldRelationshipType.swift; sourceTree = "<group>"; };
|
||||
B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FieldRelationshipProtocol.swift; sourceTree = "<group>"; };
|
||||
B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = "<group>"; };
|
||||
B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisherTests.swift; sourceTree = "<group>"; };
|
||||
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeProtocol.swift; sourceTree = "<group>"; };
|
||||
@@ -1351,6 +1479,9 @@
|
||||
children = (
|
||||
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */,
|
||||
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */,
|
||||
B56E4ED323CDB54A00E1708C /* FieldProtocol.swift */,
|
||||
B50C3ED923D0545700B29880 /* FieldAttributeProtocol.swift */,
|
||||
B57E6FAB23D30A5B000FD031 /* FieldRelationshipProtocol.swift */,
|
||||
B50564D22350CC3100482308 /* PropertyProtocol.swift */,
|
||||
B53D9E5823513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift */,
|
||||
B50E175623517DE4004F033C /* Differentiable.swift */,
|
||||
@@ -1358,6 +1489,19 @@
|
||||
name = Protocols;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B50C3EDE23D05BB200B29880 /* Coders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B50C3EDF23D062C300B29880 /* FieldCoderType.swift */,
|
||||
B50C3EE923D1601400B29880 /* FieldCoders.swift */,
|
||||
B50C3EEE23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift */,
|
||||
B50C3EF323D1623A00B29880 /* FieldCoders.NSCoding.swift */,
|
||||
B50C3EF823D1987D00B29880 /* FieldCoders.Json.swift */,
|
||||
B50C3EFD23D1AB1400B29880 /* FieldCoders.Plist.swift */,
|
||||
);
|
||||
name = Coders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B51B5C2922D43854009FA3BA /* KeyPaths */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1478,13 +1622,38 @@
|
||||
name = Migrating;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B56E4EC823CD9B2E00E1708C /* Field Properties */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B56E4EC923CD9B4800E1708C /* Field.swift */,
|
||||
B56E4ECE23CD9E4200E1708C /* Field.Stored.swift */,
|
||||
B56E4EE323CEDF0900E1708C /* Field.Virtual.swift */,
|
||||
B50C3EE423D153EA00B29880 /* Field.Coded.swift */,
|
||||
B57E6FA123D302FA000FD031 /* Field.Relationship.swift */,
|
||||
B50C3EDE23D05BB200B29880 /* Coders */,
|
||||
B56E4EDD23CEBB0400E1708C /* Supported Values */,
|
||||
);
|
||||
name = "Field Properties";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B56E4EDD23CEBB0400E1708C /* Supported Values */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B56E4EDE23CEBCF000E1708C /* FieldOptionalType.swift */,
|
||||
B56E4ED823CEB8E700E1708C /* FieldStorableType.swift */,
|
||||
B57E6FA623D305D6000FD031 /* FIeldRelationshipType.swift */,
|
||||
);
|
||||
name = "Supported Values";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B57358D71E5A7F9B0094B50A /* Dynamic Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */,
|
||||
B53304A9230BA4F7007C2BD8 /* DynamicObjectMeta.swift */,
|
||||
B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */,
|
||||
B5831B6E1F3355C300A9F647 /* Properties */,
|
||||
B50E42F623FBB91800ED476E /* ObjectProxy.swift */,
|
||||
B56E4EC823CD9B2E00E1708C /* Field Properties */,
|
||||
B5831B6E1F3355C300A9F647 /* Legacy Properties */,
|
||||
B52F74391E9B8724005F3DAC /* Dynamic Schema */,
|
||||
B5D339DC1E9489C700C880DE /* DynamicObject.swift */,
|
||||
B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */,
|
||||
@@ -1494,14 +1663,21 @@
|
||||
name = "Dynamic Models";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5831B6E1F3355C300A9F647 /* Properties */ = {
|
||||
B5831B6E1F3355C300A9F647 /* Legacy Properties */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5D33A001E96012400C880DE /* Relationship.swift */,
|
||||
B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */,
|
||||
B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */,
|
||||
B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */,
|
||||
B5D339E11E948C3600C880DE /* Value.swift */,
|
||||
B509D7B923C846E300F42824 /* Value.Required.swift */,
|
||||
B509D7BB23C847BC00F42824 /* Value.Optional.swift */,
|
||||
B5831B791F34ACBA00A9F647 /* Transformable.swift */,
|
||||
B509D7D223C84E1900F42824 /* Transformable.Required.swift */,
|
||||
B509D7D723C84E2600F42824 /* Transformable.Optional.swift */,
|
||||
);
|
||||
name = Properties;
|
||||
name = "Legacy Properties";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5A1DAC61F111BBE003CF369 /* KeyPath Utilities */ = {
|
||||
@@ -1749,6 +1925,7 @@
|
||||
B50564CC2350699700482308 /* Protocols */,
|
||||
B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */,
|
||||
B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */,
|
||||
B50C3F0223D1B01C00B29880 /* Internals.AnyFieldCoder.swift */,
|
||||
B5C976E61C6E3A5900B1AF90 /* Internals.CoreStoreFetchedResultsController.swift */,
|
||||
B5474D142227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift */,
|
||||
B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */,
|
||||
@@ -2103,10 +2280,13 @@
|
||||
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 */,
|
||||
B5CA2B081F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B50E17612351FA66004F033C /* Internals.Closure.swift in Sources */,
|
||||
B50C3EEA23D1601400B29880 /* FieldCoders.swift in Sources */,
|
||||
B5C976E71C6E3A5A00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
|
||||
B56923F51EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
B5F1DA901B9AA991007C5CBB /* ImportableUniqueObject.swift in Sources */,
|
||||
@@ -2130,6 +2310,7 @@
|
||||
B55BB4DB23503B9700C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
|
||||
B5A9921F1EA898710091A2E3 /* UserInfo.swift in Sources */,
|
||||
B54A6A551BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
|
||||
B50C3F0323D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */,
|
||||
B5D339E21E948C3600C880DE /* Value.swift in Sources */,
|
||||
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */,
|
||||
B53FBA0B1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
@@ -2139,10 +2320,12 @@
|
||||
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
|
||||
B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */,
|
||||
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
B509D7BC23C847BC00F42824 /* Value.Optional.swift in Sources */,
|
||||
B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
|
||||
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
|
||||
B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
B5831B751F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
|
||||
B57E6FA723D305D6000FD031 /* FIeldRelationshipType.swift in Sources */,
|
||||
B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B50564D32350CC3100482308 /* PropertyProtocol.swift in Sources */,
|
||||
B5D8CA762346E7590055D7D1 /* DataStack+DataSources.swift in Sources */,
|
||||
@@ -2169,13 +2352,18 @@
|
||||
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
|
||||
B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */,
|
||||
B50C3EFE23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||
B56E4EDF23CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||
B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */,
|
||||
B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */,
|
||||
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 */,
|
||||
B5BF7FAD234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift in Sources */,
|
||||
B509D7BA23C846E300F42824 /* Value.Required.swift in Sources */,
|
||||
B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B5DBE2D21C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */,
|
||||
@@ -2194,12 +2382,15 @@
|
||||
B5A1DAC81F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||
2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */,
|
||||
B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */,
|
||||
B509D7D323C84E1900F42824 /* Transformable.Required.swift in Sources */,
|
||||
B56923C41EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
|
||||
B5E84F411AFF8CCD0064E85B /* TypeErasedClauses.swift in Sources */,
|
||||
B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */,
|
||||
B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */,
|
||||
B50E42F723FBB91800ED476E /* ObjectProxy.swift in Sources */,
|
||||
B5FAD6AC1B51285300714891 /* Internals.MigrationManager.swift in Sources */,
|
||||
B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
|
||||
B50C3EE023D062C300B29880 /* FieldCoderType.swift in Sources */,
|
||||
B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */,
|
||||
B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */,
|
||||
B5BF7FCB234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */,
|
||||
@@ -2207,7 +2398,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 */,
|
||||
@@ -2215,6 +2405,7 @@
|
||||
B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */,
|
||||
B5D339DD1E9489C700C880DE /* DynamicObject.swift in Sources */,
|
||||
B5A5F2661CAEC50F004AB9AF /* CSSelect.swift in Sources */,
|
||||
B50C3EE523D153EA00B29880 /* Field.Coded.swift in Sources */,
|
||||
B5ECDBE51CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */,
|
||||
B5519A4A1CA1F4FB002BEF78 /* CSError.swift in Sources */,
|
||||
B52F742F1E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
|
||||
@@ -2224,9 +2415,12 @@
|
||||
B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */,
|
||||
B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */,
|
||||
B5215CA91FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
|
||||
B56E4EE423CEDF0900E1708C /* Field.Virtual.swift in Sources */,
|
||||
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B5E222231CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */,
|
||||
B50C3EF423D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */,
|
||||
B56E4ECF23CD9E4200E1708C /* Field.Stored.swift in Sources */,
|
||||
B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B5E2222A1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
@@ -2238,7 +2432,9 @@
|
||||
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
|
||||
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
|
||||
B50C3EF923D1987D00B29880 /* FieldCoders.Json.swift in Sources */,
|
||||
B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
|
||||
B50C3EDA23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */,
|
||||
B56923E41EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */,
|
||||
B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */,
|
||||
B533C4DB1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */,
|
||||
@@ -2251,6 +2447,7 @@
|
||||
B538BA771D15B3E30003A766 /* CoreStoreBridge.m in Sources */,
|
||||
B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */,
|
||||
B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */,
|
||||
B509D7CE23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
|
||||
B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */,
|
||||
B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */,
|
||||
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
@@ -2264,8 +2461,10 @@
|
||||
B5A991EC1E9DC2CE0091A2E3 /* VersionLock.swift in Sources */,
|
||||
B5FE4DA71C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
|
||||
B52F743D1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
|
||||
B57E6FA223D302FA000FD031 /* Field.Relationship.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 */,
|
||||
@@ -2274,7 +2473,10 @@
|
||||
B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */,
|
||||
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */,
|
||||
B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
|
||||
B57E6FAC23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */,
|
||||
B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
|
||||
B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */,
|
||||
B50C3EEF23D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */,
|
||||
B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
|
||||
B5277672234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B5DBE2CD1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
@@ -2336,11 +2538,13 @@
|
||||
B56923F61EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
82BA18A21C4BBD1D00A0916E /* CoreStoreError.swift in Sources */,
|
||||
B512608A1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */,
|
||||
B50C3EEB23D1601400B29880 /* FieldCoders.swift in Sources */,
|
||||
82BA18B21C4BBD3900A0916E /* ImportableObject.swift in Sources */,
|
||||
82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */,
|
||||
82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */,
|
||||
B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5D339D91E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
|
||||
B509D7CF23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
|
||||
82BA18CE1C4BBD7100A0916E /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
|
||||
B56923FB1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
|
||||
B55514EB1EED8BF900BAB888 /* From+Querying.swift in Sources */,
|
||||
@@ -2358,6 +2562,7 @@
|
||||
B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */,
|
||||
B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */,
|
||||
B5D339E31E948C3600C880DE /* Value.swift in Sources */,
|
||||
B50C3F0423D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */,
|
||||
B50E175323517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
82BA18C51C4BBD5300A0916E /* ListObserver.swift in Sources */,
|
||||
B53FBA0D1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
@@ -2372,6 +2577,7 @@
|
||||
B53FBA141CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
B5831B761F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
|
||||
B5BF7FB3234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B57E6FA823D305D6000FD031 /* FIeldRelationshipType.swift in Sources */,
|
||||
B5E1B5AA1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
|
||||
B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5E1B59F1CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
|
||||
@@ -2383,6 +2589,7 @@
|
||||
B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
|
||||
B55BB4DA23503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
|
||||
B5E1B5951CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
|
||||
B509D7CA23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
|
||||
18166887232B9ED60097C275 /* String+KeyPaths.swift in Sources */,
|
||||
B5ECDC2B1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
|
||||
B56923F11EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
|
||||
@@ -2397,6 +2604,8 @@
|
||||
B5831F432212700400D8604C /* Where.Expression.swift in Sources */,
|
||||
B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||
B5FE4DA81C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
|
||||
B50C3EFF23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||
B56E4EE023CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||
B5F8496D234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||
B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
@@ -2424,13 +2633,16 @@
|
||||
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 */,
|
||||
B5E1B5A41CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */,
|
||||
B596BBB71DD5BC67001DCDD9 /* FetchableSource.swift in Sources */,
|
||||
B50E42F823FBB91800ED476E /* ObjectProxy.swift in Sources */,
|
||||
B5FEC18F1C9166E600532541 /* NSPersistentStore+Setup.swift in Sources */,
|
||||
82BA18B71C4BBD3F00A0916E /* CoreStore+Querying.swift in Sources */,
|
||||
B50C3EE123D062C300B29880 /* FieldCoderType.swift in Sources */,
|
||||
82BA18AA1C4BBD3100A0916E /* BaseDataTransaction.swift in Sources */,
|
||||
82BA18A91C4BBD3100A0916E /* Into.swift in Sources */,
|
||||
82BA18D11C4BBD7100A0916E /* Internals.NotificationObserver.swift in Sources */,
|
||||
@@ -2443,7 +2655,9 @@
|
||||
82BA18C31C4BBD5300A0916E /* ObjectObserver.swift in Sources */,
|
||||
82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */,
|
||||
B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
|
||||
B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */,
|
||||
82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */,
|
||||
B50C3EE623D153EA00B29880 /* Field.Coded.swift in Sources */,
|
||||
82BA18C71C4BBD5900A0916E /* CoreStore+Migration.swift in Sources */,
|
||||
B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
82BA18C41C4BBD5300A0916E /* ListMonitor.swift in Sources */,
|
||||
@@ -2453,12 +2667,15 @@
|
||||
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B56E4EE523CEDF0900E1708C /* Field.Virtual.swift in Sources */,
|
||||
B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */,
|
||||
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B50C3EF523D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */,
|
||||
82BA18D81C4BBD7100A0916E /* Internals.WeakObject.swift in Sources */,
|
||||
B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
|
||||
B509D7BD23C8480A00F42824 /* Value.Required.swift in Sources */,
|
||||
B5215CA51FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A021E96012400C880DE /* Relationship.swift in Sources */,
|
||||
B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */,
|
||||
@@ -2467,7 +2684,9 @@
|
||||
B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */,
|
||||
B533C4DC1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */,
|
||||
B5ECDC311CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */,
|
||||
B50C3EFA23D1987D00B29880 /* FieldCoders.Json.swift in Sources */,
|
||||
B5DE522C230BD7D500A22534 /* Internals.swift in Sources */,
|
||||
B50C3EDB23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */,
|
||||
82BA18AF1C4BBD3100A0916E /* CoreStore+Transaction.swift in Sources */,
|
||||
82BA18CB1C4BBD6400A0916E /* NSManagedObject+Convenience.swift in Sources */,
|
||||
82BA18B51C4BBD3F00A0916E /* BaseDataTransaction+Querying.swift in Sources */,
|
||||
@@ -2479,6 +2698,7 @@
|
||||
82BA18AD1C4BBD3100A0916E /* UnsafeDataTransaction.swift in Sources */,
|
||||
B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
B5277673234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */,
|
||||
B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
82BA18A81C4BBD2900A0916E /* CoreStoreLogger.swift in Sources */,
|
||||
B549F65F1E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
|
||||
@@ -2493,24 +2713,32 @@
|
||||
B5AA37F2235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B57E6FA323D302FA000FD031 /* Field.Relationship.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 */,
|
||||
82BA18B91C4BBD4A00A0916E /* From.swift in Sources */,
|
||||
B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
|
||||
82BA18BE1C4BBD4A00A0916E /* Tweak.swift in Sources */,
|
||||
B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
|
||||
B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
B57E6FAD23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */,
|
||||
B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
|
||||
B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
|
||||
B50C3EF023D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */,
|
||||
B5ECDC0D1CA8161B00C7F112 /* CSGroupBy.swift in Sources */,
|
||||
B5D339E81E9493A500C880DE /* Entity.swift in Sources */,
|
||||
B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
|
||||
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;
|
||||
};
|
||||
@@ -2562,11 +2790,13 @@
|
||||
B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
B5CA2B0B1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
|
||||
B56923F81EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
B50C3EED23D1601400B29880 /* FieldCoders.swift in Sources */,
|
||||
B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */,
|
||||
B512608C1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */,
|
||||
B5ECDC151CA816E500C7F112 /* CSTweak.swift in Sources */,
|
||||
B546F9761C9C553300D5AC55 /* SetupResult.swift in Sources */,
|
||||
B53FBA161CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
B509D7D123C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
|
||||
B5ECDC271CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
|
||||
B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5D339DB1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
|
||||
@@ -2584,6 +2814,7 @@
|
||||
B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */,
|
||||
B52F74441E9B8724005F3DAC /* UnsafeDataModelSchema.swift in Sources */,
|
||||
B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
|
||||
B50C3F0623D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */,
|
||||
B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */,
|
||||
B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */,
|
||||
B5D339E51E948C3600C880DE /* Value.swift in Sources */,
|
||||
@@ -2598,6 +2829,7 @@
|
||||
B5BF7FCE234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */,
|
||||
B52DD19E1BE1F92C00949AFE /* AsynchronousDataTransaction.swift in Sources */,
|
||||
B50564D62350CC3100482308 /* PropertyProtocol.swift in Sources */,
|
||||
B57E6FAA23D305D6000FD031 /* FIeldRelationshipType.swift in Sources */,
|
||||
B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
|
||||
B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */,
|
||||
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
@@ -2609,6 +2841,7 @@
|
||||
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
|
||||
B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */,
|
||||
B50EE14523473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
|
||||
B509D7CC23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
|
||||
B55BB4D823503B9500C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
|
||||
B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */,
|
||||
18166889232B9ED80097C275 /* String+KeyPaths.swift in Sources */,
|
||||
@@ -2623,6 +2856,8 @@
|
||||
B52DD1BD1BE1F94300949AFE /* NSManagedObject+Convenience.swift in Sources */,
|
||||
B5831F4222126FED00D8604C /* KeyPathGenericBindings.swift in Sources */,
|
||||
B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */,
|
||||
B50C3F0123D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||
B56E4EE223CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||
B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */,
|
||||
B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
|
||||
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||
@@ -2650,13 +2885,16 @@
|
||||
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 */,
|
||||
B5E222271CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
B52F74481E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */,
|
||||
B50E42FA23FBB91800ED476E /* ObjectProxy.swift in Sources */,
|
||||
B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */,
|
||||
B52DD19C1BE1F92C00949AFE /* Into.swift in Sources */,
|
||||
B50C3EE323D062C300B29880 /* FieldCoderType.swift in Sources */,
|
||||
B5FE4DA51C8481E100FA6A91 /* StorageInterface.swift in Sources */,
|
||||
B596BBB91DD5BC67001DCDD9 /* FetchableSource.swift in Sources */,
|
||||
B5FE4DAA1C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
|
||||
@@ -2669,7 +2907,9 @@
|
||||
B52DD1BA1BE1F94000949AFE /* MigrationChain.swift in Sources */,
|
||||
B50392FB1C479640009900CA /* NSManagedObject+Transaction.swift in Sources */,
|
||||
B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
|
||||
B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */,
|
||||
B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */,
|
||||
B50C3EE823D153EA00B29880 /* Field.Coded.swift in Sources */,
|
||||
B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
|
||||
B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */,
|
||||
B52DD1CB1BE1F94600949AFE /* Internals.WeakObject.swift in Sources */,
|
||||
@@ -2679,12 +2919,15 @@
|
||||
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */,
|
||||
B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B56E4EE723CEDF0900E1708C /* Field.Virtual.swift in Sources */,
|
||||
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */,
|
||||
B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */,
|
||||
B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
B50C3EF723D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */,
|
||||
B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
|
||||
B509D7C123C8480B00F42824 /* Value.Required.swift in Sources */,
|
||||
B5215CA71FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A041E96012400C880DE /* Relationship.swift in Sources */,
|
||||
B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */,
|
||||
@@ -2693,7 +2936,9 @@
|
||||
B58D0C661EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */,
|
||||
B533C4DE1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */,
|
||||
B5220E141D130614009BC71E /* DataStack+Observing.swift in Sources */,
|
||||
B50C3EFC23D1987D00B29880 /* FieldCoders.Json.swift in Sources */,
|
||||
B52DD1A21BE1F92C00949AFE /* CoreStore+Transaction.swift in Sources */,
|
||||
B50C3EDD23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */,
|
||||
B5DE522E230BD7D600A22534 /* Internals.swift in Sources */,
|
||||
B5E2222E1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
B5220E191D130761009BC71E /* ListMonitor.swift in Sources */,
|
||||
@@ -2705,6 +2950,7 @@
|
||||
B52DD1A01BE1F92C00949AFE /* UnsafeDataTransaction.swift in Sources */,
|
||||
B5ECDC331CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */,
|
||||
B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */,
|
||||
B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */,
|
||||
B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
B5277675234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
@@ -2719,24 +2965,32 @@
|
||||
18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */,
|
||||
B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
|
||||
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B57E6FA523D302FA000FD031 /* Field.Relationship.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 */,
|
||||
B56923EF1EB827F6007C4DC9 /* SchemaMappingProvider.swift in Sources */,
|
||||
B53FBA081CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
|
||||
B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */,
|
||||
B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
|
||||
B5519A5C1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
|
||||
B57E6FAF23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */,
|
||||
B5DBE2D51C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
|
||||
B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
|
||||
B50C3EF223D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */,
|
||||
B5AEFAB81C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B598514B1C90289F00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */,
|
||||
B5D339EA1E9493A500C880DE /* Entity.swift in Sources */,
|
||||
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;
|
||||
};
|
||||
@@ -2788,11 +3042,13 @@
|
||||
B56923F71EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
|
||||
B56321801BD65216006C9394 /* CoreStoreError.swift in Sources */,
|
||||
B512608B1E9B252B00402229 /* NSEntityDescription+DynamicModel.swift in Sources */,
|
||||
B50C3EEC23D1601400B29880 /* FieldCoders.swift in Sources */,
|
||||
B56321AD1BD6521C006C9394 /* Internals.MigrationManager.swift in Sources */,
|
||||
B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */,
|
||||
B56321961BD65216006C9394 /* From.swift in Sources */,
|
||||
B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
|
||||
B5D339DA1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
|
||||
B509D7D023C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
|
||||
B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */,
|
||||
B56923FC1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
|
||||
B55514EC1EED8BF900BAB888 /* From+Querying.swift in Sources */,
|
||||
@@ -2810,6 +3066,7 @@
|
||||
B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */,
|
||||
B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */,
|
||||
B5D339E41E948C3600C880DE /* Value.swift in Sources */,
|
||||
B50C3F0523D1B01C00B29880 /* Internals.AnyFieldCoder.swift in Sources */,
|
||||
B50E175423517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
B53FBA0E1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
|
||||
B563219E1BD65216006C9394 /* CoreStore+Observing.swift in Sources */,
|
||||
@@ -2824,6 +3081,7 @@
|
||||
B5E1B5AB1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
|
||||
B5831B771F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
|
||||
B5BF7FB4234C97910070E741 /* DiffableDataSource.swift in Sources */,
|
||||
B57E6FA923D305D6000FD031 /* FIeldRelationshipType.swift in Sources */,
|
||||
B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
|
||||
B5E1B5A01CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
|
||||
B5ECDC261CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
|
||||
@@ -2835,6 +3093,7 @@
|
||||
B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
|
||||
B55BB4D923503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
|
||||
B5ECDC2C1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
|
||||
B509D7CB23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
|
||||
18166888232B9ED70097C275 /* String+KeyPaths.swift in Sources */,
|
||||
B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */,
|
||||
B56923F21EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
|
||||
@@ -2849,6 +3108,8 @@
|
||||
B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */,
|
||||
B5FE4DA91C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
|
||||
B5831F442212700500D8604C /* Where.Expression.swift in Sources */,
|
||||
B50C3F0023D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||
B56E4EE123CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||
B5F8496E234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||
B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
|
||||
@@ -2876,13 +3137,16 @@
|
||||
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 */,
|
||||
B5FEC1901C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */,
|
||||
B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */,
|
||||
B50E42F923FBB91800ED476E /* ObjectProxy.swift in Sources */,
|
||||
B5E1B5A51CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */,
|
||||
B596BBB81DD5BC67001DCDD9 /* FetchableSource.swift in Sources */,
|
||||
B50C3EE223D062C300B29880 /* FieldCoderType.swift in Sources */,
|
||||
B56321881BD65216006C9394 /* BaseDataTransaction.swift in Sources */,
|
||||
B56321A31BD65216006C9394 /* DataStack+Migration.swift in Sources */,
|
||||
B56321901BD65216006C9394 /* ImportableUniqueObject.swift in Sources */,
|
||||
@@ -2895,7 +3159,9 @@
|
||||
B563219B1BD65216006C9394 /* Tweak.swift in Sources */,
|
||||
B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
|
||||
B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */,
|
||||
B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */,
|
||||
B56321991BD65216006C9394 /* OrderBy.swift in Sources */,
|
||||
B50C3EE723D153EA00B29880 /* Field.Coded.swift in Sources */,
|
||||
B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
|
||||
B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */,
|
||||
B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
|
||||
@@ -2905,12 +3171,15 @@
|
||||
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
|
||||
B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */,
|
||||
B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
|
||||
B56E4EE623CEDF0900E1708C /* Field.Virtual.swift in Sources */,
|
||||
B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
|
||||
B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
|
||||
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
|
||||
B50C3EF623D1623A00B29880 /* FieldCoders.NSCoding.swift in Sources */,
|
||||
B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */,
|
||||
B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */,
|
||||
B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
|
||||
B509D7BF23C8480B00F42824 /* Value.Required.swift in Sources */,
|
||||
B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
|
||||
B5215CA61FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
|
||||
B5D33A031E96012400C880DE /* Relationship.swift in Sources */,
|
||||
@@ -2919,7 +3188,9 @@
|
||||
B56923E61EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */,
|
||||
B58D0C651EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */,
|
||||
B533C4DD1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift in Sources */,
|
||||
B50C3EFB23D1987D00B29880 /* FieldCoders.Json.swift in Sources */,
|
||||
B5ECDC321CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */,
|
||||
B50C3EDC23D0545800B29880 /* FieldAttributeProtocol.swift in Sources */,
|
||||
B5DE522D230BD7D600A22534 /* Internals.swift in Sources */,
|
||||
B56321851BD65216006C9394 /* CoreStore+Logging.swift in Sources */,
|
||||
B56321921BD65216006C9394 /* BaseDataTransaction+Querying.swift in Sources */,
|
||||
@@ -2931,6 +3202,7 @@
|
||||
B563218D1BD65216006C9394 /* CoreStore+Transaction.swift in Sources */,
|
||||
B546F96B1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
B5277674234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */,
|
||||
B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
B563218B1BD65216006C9394 /* UnsafeDataTransaction.swift in Sources */,
|
||||
B549F6601E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
|
||||
@@ -2945,24 +3217,32 @@
|
||||
B5AA37F3235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
|
||||
B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
|
||||
B57E6FA423D302FA000FD031 /* Field.Relationship.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 */,
|
||||
B56321861BD65216006C9394 /* CoreStoreLogger.swift in Sources */,
|
||||
B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
|
||||
B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */,
|
||||
B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
|
||||
B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
|
||||
B57E6FAE23D30A5B000FD031 /* FieldRelationshipProtocol.swift in Sources */,
|
||||
B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
|
||||
B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
|
||||
B50C3EF123D1605C00B29880 /* FieldCoders.DefaultNSSecureCoding.swift in Sources */,
|
||||
B5ECDC0E1CA8161B00C7F112 /* CSGroupBy.swift in Sources */,
|
||||
B5D339E91E9493A500C880DE /* Entity.swift in Sources */,
|
||||
B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
|
||||
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;
|
||||
};
|
||||
@@ -3133,7 +3413,7 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.0.3;
|
||||
MARKETING_VERSION = 7.0.4;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3156,7 +3436,7 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.0.3;
|
||||
MARKETING_VERSION = 7.0.4;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12141" systemVersion="16F73" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="15702" systemVersion="18G1012" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Place" representedClassName="CoreStoreDemo.Place" syncable="YES">
|
||||
<attribute name="latitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
@@ -20,7 +20,7 @@
|
||||
<memberEntity name="Place"/>
|
||||
</configuration>
|
||||
<elements>
|
||||
<element name="Place" positionX="261" positionY="225" width="128" height="105"/>
|
||||
<element name="Place" positionX="261" positionY="225" width="128" height="103"/>
|
||||
<element name="TimeZone" positionX="297" positionY="270" width="128" height="120"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -37,78 +37,139 @@ import CoreStore
|
||||
#endif
|
||||
|
||||
class Animal: CoreStoreObject {
|
||||
|
||||
let species = Value.Required<String>("species", initial: "Swift")
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
let color = Transformable.Optional<Color>("color")
|
||||
|
||||
@Field.Stored("species")
|
||||
var species: String = "Swift"
|
||||
|
||||
@Field.Coded("color", coder: FieldCoders.NSCoding.self)
|
||||
var color: Color? = .blue
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Dog: Animal {
|
||||
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let age = Value.Required<Int>("age", initial: 1)
|
||||
let friends = Relationship.ToManyOrdered<Dog>("friends")
|
||||
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends })
|
||||
|
||||
@Field.Stored("nickname")
|
||||
var nickname: String?
|
||||
|
||||
@Field.Stored("age")
|
||||
var age: Int = 1
|
||||
|
||||
@Field.Relationship("friends")
|
||||
var friends: [Dog]
|
||||
|
||||
@Field.Relationship("friendedBy", inverse: \.$friends)
|
||||
var friendedBy: Set<Dog>
|
||||
}
|
||||
|
||||
struct CustomType {
|
||||
var string = "customString"
|
||||
}
|
||||
|
||||
enum Job: String {
|
||||
|
||||
case unemployed
|
||||
case engineer
|
||||
case doctor
|
||||
case lawyer
|
||||
|
||||
init?(data: Data) {
|
||||
|
||||
guard
|
||||
let rawValue = String(data: data, encoding: .utf8),
|
||||
let value = Self.init(rawValue: rawValue)
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self = value
|
||||
}
|
||||
|
||||
func toData() -> Data {
|
||||
|
||||
return Data(self.rawValue.utf8)
|
||||
}
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
let title = Value.Required<String>(
|
||||
|
||||
@Field.Stored(
|
||||
"title",
|
||||
initial: "Mr.",
|
||||
customSetter: Person.setTitle
|
||||
customSetter: { (object, field, newValue) in
|
||||
field.primitiveValue = newValue
|
||||
object.$displayName.primitiveValue = nil
|
||||
}
|
||||
)
|
||||
|
||||
let name = Value.Required<String>(
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored(
|
||||
"name",
|
||||
initial: "",
|
||||
customSetter: Person.setName
|
||||
customSetter: { (object, field, newValue) in
|
||||
field.primitiveValue = newValue
|
||||
object.$displayName.primitiveValue = nil
|
||||
}
|
||||
)
|
||||
|
||||
let displayName = Value.Optional<String>(
|
||||
var name: String = ""
|
||||
|
||||
@Field.Virtual(
|
||||
"displayName",
|
||||
isTransient: true,
|
||||
customGetter: Person.getDisplayName(_:),
|
||||
customGetter: Person.getDisplayName(_:_:),
|
||||
affectedByKeyPaths: Person.keyPathsAffectingDisplayName()
|
||||
)
|
||||
var displayName: String?
|
||||
|
||||
let spouse = Relationship.ToOne<Person>("spouse")
|
||||
|
||||
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master })
|
||||
@Field.Virtual(
|
||||
"customType",
|
||||
customGetter: { (object, field) in
|
||||
|
||||
private let _spouse = Relationship.ToOne<Person>("_spouseInverse", inverse: { $0.spouse })
|
||||
if let value = field.primitiveValue {
|
||||
|
||||
|
||||
private static func setTitle(_ partialObject: PartialObject<Person>, _ newValue: String) {
|
||||
|
||||
partialObject.setPrimitiveValue(newValue, for: { $0.title })
|
||||
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
|
||||
}
|
||||
|
||||
private static func setName(_ partialObject: PartialObject<Person>, _ newValue: String) {
|
||||
|
||||
partialObject.setPrimitiveValue(newValue, for: { $0.name })
|
||||
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
|
||||
}
|
||||
|
||||
static func getDisplayName(_ partialObject: PartialObject<Person>) -> String? {
|
||||
|
||||
if let displayName = partialObject.primitiveValue(for: { $0.displayName }) {
|
||||
|
||||
return displayName
|
||||
return value
|
||||
}
|
||||
let value = CustomType()
|
||||
field.primitiveValue = value
|
||||
return value
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
)
|
||||
var customField: CustomType
|
||||
|
||||
@Field.Coded(
|
||||
"job",
|
||||
coder: (
|
||||
encode: { $0.toData() },
|
||||
decode: { $0.flatMap(Job.init(data:)) ?? .unemployed }
|
||||
)
|
||||
)
|
||||
var job: Job = .unemployed
|
||||
|
||||
@Field.Relationship("spouse")
|
||||
var spouse: Person?
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Animal>
|
||||
|
||||
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
|
||||
private var spouseInverse: Person?
|
||||
|
||||
static func getDisplayName(_ object: ObjectProxy<Person>, _ field: ObjectProxy<Person>.FieldProxy<String?>) -> String? {
|
||||
|
||||
if let value = field.primitiveValue {
|
||||
|
||||
return value
|
||||
}
|
||||
let title = object.$title.value
|
||||
let name = object.$name.value
|
||||
let value = "\(title) \(name)"
|
||||
field.primitiveValue = value
|
||||
return value
|
||||
}
|
||||
|
||||
static func keyPathsAffectingDisplayName() -> Set<String> {
|
||||
|
||||
return [
|
||||
String(keyPath: \Person.title),
|
||||
String(keyPath: \Person.name)
|
||||
String(keyPath: \Person.$title),
|
||||
String(keyPath: \Person.$name)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -132,19 +193,19 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
versionLock: [
|
||||
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
|
||||
"Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7],
|
||||
"Person": [0x2831cf046084d96d, 0xbe19b13ace54641, 0x635a082728b0f6f0, 0x3d4ef2dd4b74a87c]
|
||||
"Person": [0xf3e6ba6016bbedc6, 0x50dedf64f0eba490, 0xa32088a0ee83468d, 0xb72d1d0b37bd0992]
|
||||
]
|
||||
)
|
||||
)
|
||||
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,27 +217,28 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let animal = transaction.create(Into<Animal>())
|
||||
XCTAssertEqual(animal.species.value, "Swift")
|
||||
XCTAssertTrue(type(of: animal.species.value) == String.self)
|
||||
XCTAssertEqual(animal.species, "Swift")
|
||||
XCTAssertTrue(type(of: animal.species) == String.self)
|
||||
XCTAssertEqual(animal.color, Color.blue)
|
||||
|
||||
animal.species .= "Sparrow"
|
||||
XCTAssertEqual(animal.species.value, "Sparrow")
|
||||
animal.species = "Sparrow"
|
||||
XCTAssertEqual(animal.species, "Sparrow")
|
||||
|
||||
animal.color .= .yellow
|
||||
XCTAssertEqual(animal.color.value, Color.yellow)
|
||||
animal.color = .yellow
|
||||
XCTAssertEqual(animal.color, Color.yellow)
|
||||
|
||||
for property in Animal.metaProperties(includeSuperclasses: true) {
|
||||
|
||||
switch property.keyPath {
|
||||
|
||||
case String(keyPath: \Animal.species):
|
||||
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>)
|
||||
case String(keyPath: \Animal.$species):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
|
||||
|
||||
case String(keyPath: \Animal.master):
|
||||
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
|
||||
case String(keyPath: \Animal.$master):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
|
||||
|
||||
case String(keyPath: \Animal.color):
|
||||
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>)
|
||||
case String(keyPath: \Animal.$color):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
|
||||
|
||||
default:
|
||||
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
|
||||
@@ -184,34 +246,34 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
}
|
||||
|
||||
let dog = transaction.create(Into<Dog>())
|
||||
XCTAssertEqual(dog.species.value, "Swift")
|
||||
XCTAssertEqual(dog.nickname.value, nil)
|
||||
XCTAssertEqual(dog.age.value, 1)
|
||||
XCTAssertEqual(dog.species, "Swift")
|
||||
XCTAssertEqual(dog.nickname, nil)
|
||||
XCTAssertEqual(dog.age, 1)
|
||||
|
||||
for property in Dog.metaProperties(includeSuperclasses: true) {
|
||||
|
||||
switch property.keyPath {
|
||||
|
||||
case String(keyPath: \Dog.species):
|
||||
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>)
|
||||
case String(keyPath: \Dog.$species):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
|
||||
|
||||
case String(keyPath: \Dog.master):
|
||||
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
|
||||
case String(keyPath: \Dog.$master):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
|
||||
|
||||
case String(keyPath: \Dog.color):
|
||||
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>)
|
||||
case String(keyPath: \Dog.$color):
|
||||
XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
|
||||
|
||||
case String(keyPath: \Dog.nickname):
|
||||
XCTAssertTrue(property is ValueContainer<Dog>.Optional<String>)
|
||||
case String(keyPath: \Dog.$nickname):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Stored<String?>)
|
||||
|
||||
case String(keyPath: \Dog.age):
|
||||
XCTAssertTrue(property is ValueContainer<Dog>.Required<Int>)
|
||||
case String(keyPath: \Dog.$age):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Stored<Int>)
|
||||
|
||||
case String(keyPath: \Dog.friends):
|
||||
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyOrdered<Dog>)
|
||||
case String(keyPath: \Dog.$friends):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Relationship<[Dog]>)
|
||||
|
||||
case String(keyPath: \Dog.friendedBy):
|
||||
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyUnordered<Dog>)
|
||||
case String(keyPath: \Dog.$friendedBy):
|
||||
XCTAssertTrue(property is FieldContainer<Dog>.Relationship<Set<Dog>>)
|
||||
|
||||
default:
|
||||
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
|
||||
@@ -231,17 +293,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,28 +312,30 @@ 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<Person>())
|
||||
XCTAssertTrue(person.pets.value.isEmpty)
|
||||
XCTAssertTrue(person.pets.isEmpty)
|
||||
XCTAssertEqual(person.customField.string, "customString")
|
||||
XCTAssertEqual(person.job, .unemployed)
|
||||
|
||||
XCTAssertEqual(
|
||||
person.rawObject!
|
||||
@@ -280,7 +344,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,46 +352,66 @@ 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)
|
||||
XCTAssertEqual(person.job, personSnapshot1.$job)
|
||||
|
||||
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")
|
||||
|
||||
person.customField.string = "newCustomString"
|
||||
XCTAssertEqual(person.customField.string, "newCustomString")
|
||||
|
||||
person.job = .engineer
|
||||
XCTAssertEqual(person.job, .engineer)
|
||||
|
||||
let personSnapshot2 = person.asSnapshot(in: transaction)!
|
||||
XCTAssertEqual(person.name.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)
|
||||
XCTAssertEqual(person.job, personSnapshot2.$job)
|
||||
|
||||
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(personSnapshot1.$job, .unemployed)
|
||||
XCTAssertEqual(personSnapshot2.$name, "John")
|
||||
XCTAssertEqual(personSnapshot2.$displayName, "Sir John")
|
||||
XCTAssertEqual(personSnapshot2.$job, .engineer)
|
||||
XCTAssertEqual(personSnapshot3.$name, "James")
|
||||
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
|
||||
XCTAssertEqual(personSnapshot3.$job, .engineer)
|
||||
|
||||
|
||||
|
||||
person.pets.value.insert(dog)
|
||||
person.pets.insert(dog)
|
||||
XCTAssertEqual(person.pets.count, 1)
|
||||
XCTAssertEqual(person.pets.value.first, dog)
|
||||
XCTAssertEqual(person.pets.value.first?.master.value, person)
|
||||
XCTAssertEqual(dog.master.value, person)
|
||||
XCTAssertEqual(dog.master.value?.pets.value.first, dog)
|
||||
XCTAssertEqual(person.pets.first, dog)
|
||||
XCTAssertEqual(person.pets.first?.master, person)
|
||||
XCTAssertEqual(dog.master, person)
|
||||
XCTAssertEqual(dog.master?.pets.first, dog)
|
||||
},
|
||||
success: { _ in
|
||||
|
||||
|
||||
let person = try! stack.fetchOne(From<Person>())
|
||||
XCTAssertNotNil(person)
|
||||
|
||||
let personPublisher = person!.asPublisher(in: stack)
|
||||
XCTAssertEqual(personPublisher.$name, "John")
|
||||
XCTAssertEqual(personPublisher.$displayName, "Sir John")
|
||||
XCTAssertEqual(personPublisher.$job, .engineer)
|
||||
|
||||
updateDone.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
@@ -338,59 +422,67 @@ class DynamicModelTests: BaseTestDataTestCase {
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let p1 = Where<Animal>({ $0.species == "Sparrow" })
|
||||
let p1 = Where<Animal>({ $0.$species == "Sparrow" })
|
||||
XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow"))
|
||||
|
||||
let bird = try transaction.fetchOne(From<Animal>(), p1)
|
||||
XCTAssertNotNil(bird)
|
||||
XCTAssertEqual(bird!.species.value, "Sparrow")
|
||||
XCTAssertEqual(bird!.species, "Sparrow")
|
||||
XCTAssertEqual(bird!.color, Color.yellow)
|
||||
|
||||
let p2 = Where<Dog>({ $0.nickname == "Spot" })
|
||||
let p2 = Where<Dog>({ $0.$nickname == "Spot" })
|
||||
XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot"))
|
||||
|
||||
let dog = try transaction.fetchOne(From<Dog>().where(\.nickname == "Spot"))
|
||||
let dog = try transaction.fetchOne(From<Dog>().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<Person>())
|
||||
XCTAssertNotNil(person)
|
||||
XCTAssertEqual(person!.pets.value.first, dog)
|
||||
XCTAssertEqual(person!.name, "John")
|
||||
XCTAssertEqual(person!.title, "Sir")
|
||||
XCTAssertEqual(person!.displayName, "Sir John")
|
||||
XCTAssertEqual(person!.customField.string, "customString")
|
||||
XCTAssertEqual(person!.job, .engineer)
|
||||
XCTAssertEqual(person!.pets.first, dog)
|
||||
|
||||
let p3 = Where<Dog>({ $0.age == 10 })
|
||||
let p3 = Where<Dog>({ $0.$age == 10 })
|
||||
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
|
||||
|
||||
let totalAge = try transaction.queryValue(From<Dog>().select(Int.self, .sum(\Dog.age)))
|
||||
let totalAge = try transaction.queryValue(
|
||||
From<Dog>().select(Int.self, .sum(\.$age))
|
||||
)
|
||||
XCTAssertEqual(totalAge, 1)
|
||||
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>()
|
||||
.where(\Animal.species == "Dog" && \Dog.age == 10)
|
||||
.where(\Animal.$species == "Dog" && \Dog.$age == 10)
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>()
|
||||
.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<Dog>(),
|
||||
Where<Dog>({ $0.age > 10 && $0.age <= 15 })
|
||||
Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.species == "Dog" && $0.age == 10 })
|
||||
Where<Dog>({ $0.$species == "Dog" && $0.$age == 10 })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.age == 10 && $0.species == "Dog" })
|
||||
Where<Dog>({ $0.$age == 10 && $0.$species == "Dog" })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
Where<Dog>({ $0.age > 10 && $0.age <= 15 })
|
||||
Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
|
||||
)
|
||||
_ = try transaction.fetchAll(
|
||||
From<Dog>(),
|
||||
(\Dog.age > 10 && \Dog.age <= 15)
|
||||
(\Dog.$age > 10 && \Dog.$age <= 15)
|
||||
)
|
||||
},
|
||||
success: { _ in
|
||||
@@ -409,8 +501,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -67,7 +67,7 @@ final class WhereTests: XCTestCase {
|
||||
dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() {
|
||||
|
||||
XCTAssertAllEqual(String(keyPath: \TestEntity1.testEntityID), "testEntityID")
|
||||
XCTAssertAllEqual(String(keyPath: \Animal.color), "color")
|
||||
XCTAssertAllEqual(String(keyPath: \Animal.$color), "color")
|
||||
}
|
||||
|
||||
@objc
|
||||
@@ -104,18 +104,18 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"master.pets",
|
||||
(\Animal.master ~ \.pets).description,
|
||||
String(keyPath: \Animal.master ~ \.pets)
|
||||
(\Animal.$master ~ \.$pets).description,
|
||||
String(keyPath: \Animal.$master ~ \.$pets)
|
||||
)
|
||||
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",
|
||||
(\Animal.master ~ \.pets ~ \.master).description,
|
||||
String(keyPath: \Animal.master ~ \.pets ~ \.master)
|
||||
(\Animal.$master ~ \.$pets ~ \.$master).description,
|
||||
String(keyPath: \Animal.$master ~ \.$pets ~ \.$master)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -138,8 +138,8 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"master.pets.@count",
|
||||
(\Animal.master ~ \.pets).count().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).count())
|
||||
(\Animal.$master ~ \.$pets).count().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).count())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -162,13 +162,13 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"ANY master.pets",
|
||||
(\Animal.master ~ \.pets).any().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).any())
|
||||
(\Animal.$master ~ \.$pets).any().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).any())
|
||||
)
|
||||
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())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -191,13 +191,13 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"ALL master.pets",
|
||||
(\Animal.master ~ \.pets).all().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).all())
|
||||
(\Animal.$master ~ \.$pets).all().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).all())
|
||||
)
|
||||
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())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -220,13 +220,13 @@ final class WhereTests: XCTestCase {
|
||||
|
||||
XCTAssertAllEqual(
|
||||
"NONE master.pets",
|
||||
(\Animal.master ~ \.pets).none().description,
|
||||
String(keyPath: (\Animal.master ~ \.pets).none())
|
||||
(\Animal.$master ~ \.$pets).none().description,
|
||||
String(keyPath: (\Animal.$master ~ \.$pets).none())
|
||||
)
|
||||
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<Animal> = (\.master ~ \.name) == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$name) == dummy
|
||||
let predicate = NSPredicate(format: "master.name == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -265,7 +265,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.spouse ~ \.name) == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$spouse ~ \.$name) == dummy
|
||||
let predicate = NSPredicate(format: "master.spouse.name == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -283,7 +283,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets).count() == count
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets).count() == count
|
||||
let predicate = NSPredicate(format: "master.pets.@count == %d", count)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -301,7 +301,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).any() == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).any() == dummy
|
||||
let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -319,7 +319,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).all() == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).all() == dummy
|
||||
let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
@@ -337,7 +337,7 @@ final class WhereTests: XCTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).none() == dummy
|
||||
let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).none() == dummy
|
||||
let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy)
|
||||
XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
|
||||
XCTAssertAllEqual(whereClause.predicate, predicate)
|
||||
|
||||
@@ -19,8 +19,8 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
<br />
|
||||
</p>
|
||||
|
||||
* **Swift 5.1:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
|
||||
* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2)
|
||||
* **Swift 5.2:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
|
||||
* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2), [Swift 5.1](https://github.com/JohnEstropia/CoreStore/tree/7.0.4)
|
||||
|
||||
Upgrading from CoreStore 6.x (swift 5.0) to 7.x (Swift 5.1)? Check out the [🆕 features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
|
||||
|
||||
@@ -1888,7 +1888,7 @@ From the **File** - **Swift Packages** - **Add Package Dependency…** menu, sea
|
||||
```
|
||||
CoreStore
|
||||
```
|
||||
where `JohnEstropia` is the *Owner*. Then add to your project.
|
||||
where `JohnEstropia` is the *Owner* (forks may appear as well). Then add to your project.
|
||||
|
||||
|
||||
### Objective-C support
|
||||
|
||||
@@ -29,17 +29,20 @@ import CoreData
|
||||
|
||||
// MARK: - AttributeProtocol
|
||||
|
||||
internal protocol AttributeProtocol: PropertyProtocol {
|
||||
|
||||
static var attributeType: NSAttributeType { get }
|
||||
|
||||
var isOptional: Bool { get }
|
||||
var isTransient: Bool { get }
|
||||
var allowsExternalBinaryDataStorage: Bool { get }
|
||||
var versionHashModifier: () -> String? { get }
|
||||
var renamingIdentifier: () -> String? { get }
|
||||
var defaultValue: () -> Any? { get }
|
||||
var affectedByKeyPaths: () -> Set<String> { get }
|
||||
internal protocol AttributeProtocol: AnyObject, PropertyProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
attributeType: NSAttributeType,
|
||||
isOptional: Bool,
|
||||
isTransient: Bool,
|
||||
allowsExternalBinaryDataStorage: Bool,
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
affectedByKeyPaths: Set<String>,
|
||||
defaultValue: Any?
|
||||
)
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
var rawObject: CoreStoreManagedObject? { get set }
|
||||
var getter: CoreStoreManagedObject.CustomGetter? { get }
|
||||
var setter: CoreStoreManagedObject.CustomSetter? { get }
|
||||
|
||||
@@ -44,6 +44,17 @@ public final class CSError: NSError {
|
||||
*/
|
||||
@objc
|
||||
public static let errorDomain = CoreStoreErrorDomain
|
||||
|
||||
public var bridgeToSwift: CoreStoreError {
|
||||
|
||||
if let swift = self.swiftError {
|
||||
|
||||
return swift
|
||||
}
|
||||
let swift = CoreStoreError(_bridgedNSError: self) ?? .unknown
|
||||
self.swiftError = swift
|
||||
return swift
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSObject
|
||||
@@ -88,21 +99,7 @@ public final class CSError: NSError {
|
||||
}
|
||||
|
||||
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
|
||||
extension CSError: CoreStoreObjectiveCType {
|
||||
|
||||
// MARK: CoreStoreObjectiveCType
|
||||
|
||||
public var bridgeToSwift: CoreStoreError {
|
||||
|
||||
if let swift = self.swiftError {
|
||||
|
||||
return swift
|
||||
}
|
||||
let swift = CoreStoreError(_bridgedNSError: self) ?? .unknown
|
||||
self.swiftError = swift
|
||||
return swift
|
||||
}
|
||||
}
|
||||
extension CSError: CoreStoreObjectiveCType {}
|
||||
|
||||
|
||||
// MARK: - CSErrorCode
|
||||
@@ -156,16 +153,7 @@ public enum CSErrorCode: Int {
|
||||
|
||||
// MARK: - CoreStoreError
|
||||
|
||||
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
|
||||
extension CoreStoreError: CoreStoreSwiftType, _ObjectiveCBridgeableError {
|
||||
|
||||
// MARK: CoreStoreSwiftType
|
||||
|
||||
public var bridgeToObjectiveC: CSError {
|
||||
|
||||
return CSError(self)
|
||||
}
|
||||
|
||||
extension CoreStoreError: _ObjectiveCBridgeableError {
|
||||
|
||||
// MARK: _ObjectiveCBridgeableError
|
||||
|
||||
@@ -265,9 +253,11 @@ extension CoreStoreError: CoreStoreSwiftType, _ObjectiveCBridgeableError {
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
// MARK: - Error
|
||||
|
||||
extension Error {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var bridgeToSwift: CoreStoreError {
|
||||
|
||||
@@ -303,3 +293,17 @@ extension Error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreError
|
||||
|
||||
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
|
||||
extension CoreStoreError: CoreStoreSwiftType {
|
||||
|
||||
// MARK: CoreStoreSwiftType
|
||||
|
||||
public var bridgeToObjectiveC: CSError {
|
||||
|
||||
return CSError(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,6 +27,33 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension DynamicObject where Self: CoreStoreObject {
|
||||
|
||||
public func observe<O, V>(_ keyPath: KeyPath<Self, FieldContainer<O>.Stored<V>>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, CoreStoreObjectValueDiff<V>) -> Void) -> CoreStoreObjectKeyValueObservation {
|
||||
|
||||
let result = _CoreStoreObjectKeyValueObservation(
|
||||
object: self.rawObject!,
|
||||
keyPath: self[keyPath: keyPath].keyPath,
|
||||
callback: { (object, kind, newValue, oldValue, _, isPrior) in
|
||||
|
||||
let notification = CoreStoreObjectValueDiff<V>(
|
||||
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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Person>().where({ $0.nickname == "John" }))
|
||||
```
|
||||
*/
|
||||
public static func == (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
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<Person>().where({ $0.nickname != "John" }))
|
||||
```
|
||||
*/
|
||||
public static func != (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
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<Person>().where({ $0.age < 20 }))
|
||||
```
|
||||
*/
|
||||
public static func < (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
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<Person>().where({ $0.age > 20 }))
|
||||
```
|
||||
*/
|
||||
public static func > (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
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<Person>().where({ $0.age <= 20 }))
|
||||
```
|
||||
*/
|
||||
public static func <= (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
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<Person>().where({ $0.age >= 20 }))
|
||||
```
|
||||
*/
|
||||
public static func >= (_ attribute: Self, _ value: V) -> Where<O> {
|
||||
|
||||
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<Dog>().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname }))
|
||||
```
|
||||
*/
|
||||
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: Self) -> Where<O> where S.Iterator.Element == V {
|
||||
|
||||
return Where(attribute.keyPath, isMemberOf: sequence)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ValueContainer.Required
|
||||
|
||||
extension ValueContainer.Required {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -209,20 +209,23 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
let rawModel = NSManagedObjectModel()
|
||||
var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||
var allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||
var allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||
for entity in self.allEntities {
|
||||
|
||||
let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription(
|
||||
let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = self.entityDescription(
|
||||
for: entity,
|
||||
initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:)
|
||||
)
|
||||
entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription)
|
||||
allCustomGettersSetters[entity] = customGetterSetterByKeyPaths
|
||||
allFieldCoders[entity] = fieldCoders
|
||||
}
|
||||
CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
|
||||
CoreStoreSchema.thirdPassConnectInheritanceTreeAndIndexes(for: entityDescriptionsByEntity)
|
||||
CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses(
|
||||
for: entityDescriptionsByEntity,
|
||||
allCustomGettersSetters: allCustomGettersSetters
|
||||
allCustomGettersSetters: allCustomGettersSetters,
|
||||
allFieldCoders: allFieldCoders
|
||||
)
|
||||
|
||||
rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
|
||||
@@ -254,22 +257,46 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
|
||||
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||
private var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||
private var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||
private weak var cachedRawModel: NSManagedObjectModel?
|
||||
|
||||
private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]) {
|
||||
private func entityDescription(
|
||||
for entity: DynamicEntity,
|
||||
initializer: (DynamicEntity, ModelVersion) -> (
|
||||
entity: NSEntityDescription,
|
||||
customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter],
|
||||
fieldCoders: [KeyPathString: Internals.AnyFieldCoder]
|
||||
)
|
||||
) -> (
|
||||
entity: NSEntityDescription,
|
||||
customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter],
|
||||
fieldCoders: [KeyPathString: Internals.AnyFieldCoder]
|
||||
) {
|
||||
|
||||
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
|
||||
|
||||
return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:])
|
||||
return (
|
||||
cachedEntityDescription,
|
||||
self.customGettersSettersByEntity[entity] ?? [:],
|
||||
self.fieldCodersByEntity[entity] ?? [:]
|
||||
)
|
||||
}
|
||||
let modelVersion = self.modelVersion
|
||||
let (entityDescription, customGetterSetterByKeyPaths) = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) })
|
||||
let (entityDescription, customGetterSetterByKeyPaths, fieldCoders) = withoutActuallyEscaping(
|
||||
initializer,
|
||||
do: { $0(entity, modelVersion) }
|
||||
)
|
||||
self.entityDescriptionsByEntity[entity] = entityDescription
|
||||
self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths
|
||||
return (entityDescription, customGetterSetterByKeyPaths)
|
||||
self.fieldCodersByEntity[entity] = fieldCoders
|
||||
return (entityDescription, customGetterSetterByKeyPaths, fieldCoders)
|
||||
}
|
||||
|
||||
private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]) {
|
||||
|
||||
private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (
|
||||
entity: NSEntityDescription,
|
||||
customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter],
|
||||
fieldCoders: [KeyPathString: Internals.AnyFieldCoder]
|
||||
) {
|
||||
|
||||
let entityDescription = NSEntityDescription()
|
||||
entityDescription.coreStoreEntity = entity
|
||||
@@ -280,29 +307,73 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
|
||||
var keyPathsByAffectedKeyPaths: [KeyPathString: Set<KeyPathString>] = [:]
|
||||
var customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter] = [:]
|
||||
var fieldCoders: [KeyPathString: Internals.AnyFieldCoder] = [:]
|
||||
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
|
||||
|
||||
var propertyDescriptions: [NSPropertyDescription] = []
|
||||
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
|
||||
|
||||
let valueTransformer = entityDescriptionValues.valueTransformer
|
||||
description.valueTransformerName = valueTransformer?.transformerName.rawValue
|
||||
|
||||
propertyDescriptions.append(description)
|
||||
|
||||
keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths
|
||||
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
|
||||
fieldCoders[attribute.keyPath] = valueTransformer
|
||||
|
||||
case let relationship as FieldRelationshipProtocol:
|
||||
Internals.assert(
|
||||
!NSManagedObject.instancesRespond(to: Selector(relationship.keyPath)),
|
||||
"Relationship Property name \"\(String(reflecting: entity.type)).\(relationship.keyPath)\" is not allowed because it collides with \"\(String(reflecting: NSManagedObject.self)).\(relationship.keyPath)\""
|
||||
)
|
||||
let entityDescriptionValues = relationship.entityDescriptionValues()
|
||||
let description = NSRelationshipDescription()
|
||||
description.name = relationship.keyPath
|
||||
description.minCount = entityDescriptionValues.minCount
|
||||
description.maxCount = entityDescriptionValues.maxCount
|
||||
description.isOrdered = entityDescriptionValues.isOrdered
|
||||
description.deleteRule = entityDescriptionValues.deleteRule
|
||||
description.versionHashModifier = entityDescriptionValues.versionHashModifier
|
||||
description.renamingIdentifier = entityDescriptionValues.renamingIdentifier
|
||||
propertyDescriptions.append(description)
|
||||
keyPathsByAffectedKeyPaths[relationship.keyPath] = entityDescriptionValues.affectedByKeyPaths
|
||||
|
||||
case let attribute as AttributeProtocol:
|
||||
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 = Swift.type(of: attribute).attributeType
|
||||
description.isOptional = attribute.isOptional
|
||||
description.defaultValue = attribute.defaultValue()
|
||||
description.isTransient = attribute.isTransient
|
||||
description.allowsExternalBinaryDataStorage = attribute.allowsExternalBinaryDataStorage
|
||||
description.versionHashModifier = attribute.versionHashModifier()
|
||||
description.renamingIdentifier = attribute.renamingIdentifier()
|
||||
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] = attribute.affectedByKeyPaths()
|
||||
keyPathsByAffectedKeyPaths[attribute.keyPath] = entityDescriptionValues.affectedByKeyPaths
|
||||
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
|
||||
|
||||
case let relationship as RelationshipProtocol:
|
||||
@@ -310,16 +381,17 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
!NSManagedObject.instancesRespond(to: Selector(relationship.keyPath)),
|
||||
"Relationship Property name \"\(String(reflecting: entity.type)).\(relationship.keyPath)\" is not allowed because it collides with \"\(String(reflecting: NSManagedObject.self)).\(relationship.keyPath)\""
|
||||
)
|
||||
let entityDescriptionValues = relationship.entityDescriptionValues()
|
||||
let description = NSRelationshipDescription()
|
||||
description.name = relationship.keyPath
|
||||
description.minCount = relationship.minCount
|
||||
description.maxCount = relationship.maxCount
|
||||
description.isOrdered = relationship.isOrdered
|
||||
description.deleteRule = relationship.deleteRule
|
||||
description.versionHashModifier = relationship.versionHashModifier()
|
||||
description.renamingIdentifier = relationship.renamingIdentifier()
|
||||
description.minCount = entityDescriptionValues.minCount
|
||||
description.maxCount = entityDescriptionValues.maxCount
|
||||
description.isOrdered = entityDescriptionValues.isOrdered
|
||||
description.deleteRule = entityDescriptionValues.deleteRule
|
||||
description.versionHashModifier = entityDescriptionValues.versionHashModifier
|
||||
description.renamingIdentifier = entityDescriptionValues.renamingIdentifier
|
||||
propertyDescriptions.append(description)
|
||||
keyPathsByAffectedKeyPaths[relationship.keyPath] = relationship.affectedByKeyPaths()
|
||||
keyPathsByAffectedKeyPaths[relationship.keyPath] = entityDescriptionValues.affectedByKeyPaths
|
||||
|
||||
default:
|
||||
continue
|
||||
@@ -329,7 +401,7 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
}
|
||||
entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type)
|
||||
entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths
|
||||
return (entityDescription, customGetterSetterByKeyPaths)
|
||||
return (entityDescription, customGetterSetterByKeyPaths, fieldCoders)
|
||||
}
|
||||
|
||||
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
|
||||
@@ -382,14 +454,34 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
for property in entityType.metaProperties(includeSuperclasses: false) {
|
||||
|
||||
switch property {
|
||||
|
||||
case let relationship as FieldRelationshipProtocol:
|
||||
let (destinationType, destinationKeyPath) = relationship.entityDescriptionValues().inverse
|
||||
let destinationEntity = findEntity(for: destinationType)
|
||||
let description = relationshipsByName[relationship.keyPath]!
|
||||
description.destinationEntity = entityDescriptionsByEntity[destinationEntity]!
|
||||
|
||||
if let destinationKeyPath = destinationKeyPath {
|
||||
|
||||
let inverseRelationshipDescription = findInverseRelationshipMatching(
|
||||
destinationEntity: destinationEntity,
|
||||
destinationKeyPath: destinationKeyPath
|
||||
)
|
||||
description.inverseRelationship = inverseRelationshipDescription
|
||||
|
||||
inverseRelationshipDescription.inverseRelationship = description
|
||||
inverseRelationshipDescription.destinationEntity = entityDescription
|
||||
|
||||
description.destinationEntity!.properties = description.destinationEntity!.properties
|
||||
}
|
||||
|
||||
case let relationship as RelationshipProtocol:
|
||||
let (destinationType, destinationKeyPath) = relationship.inverse
|
||||
let (destinationType, destinationKeyPath) = relationship.entityDescriptionValues().inverse
|
||||
let destinationEntity = findEntity(for: destinationType)
|
||||
let description = relationshipsByName[relationship.keyPath]!
|
||||
description.destinationEntity = entityDescriptionsByEntity[destinationEntity]!
|
||||
|
||||
if let destinationKeyPath = destinationKeyPath() {
|
||||
if let destinationKeyPath = destinationKeyPath {
|
||||
|
||||
let inverseRelationshipDescription = findInverseRelationshipMatching(
|
||||
destinationEntity: destinationEntity,
|
||||
@@ -502,7 +594,11 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
}
|
||||
}
|
||||
|
||||
private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]]) {
|
||||
private static func fourthPassSynthesizeManagedObjectClasses(
|
||||
for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription],
|
||||
allCustomGettersSetters: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]],
|
||||
allFieldCoders: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]]
|
||||
) {
|
||||
|
||||
func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]?) {
|
||||
|
||||
@@ -615,5 +711,13 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity]
|
||||
)
|
||||
}
|
||||
|
||||
_ = allFieldCoders
|
||||
.flatMap({ (_, values) in values })
|
||||
.reduce(
|
||||
into: [:] as [NSValueTransformerName: Internals.AnyFieldCoder],
|
||||
{ (result, element) in result[element.value.transformerName] = element.value }
|
||||
)
|
||||
.forEach({ (_, fieldCoder) in fieldCoder.register() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
@@ -214,3 +216,5 @@ extension DiffableDataSource {
|
||||
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
@@ -209,3 +211,5 @@ extension DiffableDataSource.Target {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(macOS)
|
||||
#if canImport(UIKit) || canImport(AppKit)
|
||||
|
||||
// MARK: - DiffableDataSource
|
||||
|
||||
|
||||
@@ -114,7 +114,8 @@ extension NSManagedObject: DynamicObject {
|
||||
|
||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
||||
|
||||
return unsafeDowncast(object, to: self)
|
||||
// unsafeDowncast fails debug assertion starting Swift 5.2
|
||||
return _unsafeUncheckedDowncast(object, to: self)
|
||||
}
|
||||
|
||||
public static func cs_matches(object: NSManagedObject) -> Bool {
|
||||
@@ -153,41 +154,102 @@ 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:
|
||||
Internals.assert(
|
||||
object.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(type(of: property).dynamicObjectType))'s value outside it's designated queue."
|
||||
)
|
||||
attributes[property.keyPath] = type(of: property).read(
|
||||
field: property,
|
||||
for: object.rawObject!
|
||||
)
|
||||
|
||||
case let property as FieldRelationshipProtocol:
|
||||
Internals.assert(
|
||||
object.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(type(of: property).dynamicObjectType))'s value outside it's designated queue."
|
||||
)
|
||||
attributes[property.keyPath] = type(of: property).valueForSnapshot(
|
||||
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:
|
||||
Internals.assert(
|
||||
object.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(type(of: property).dynamicObjectType))'s value outside it's designated queue."
|
||||
)
|
||||
values[property.keyPath] = type(of: property).read(
|
||||
field: property,
|
||||
for: rawObject
|
||||
)
|
||||
|
||||
case let property as FieldRelationshipProtocol:
|
||||
Internals.assert(
|
||||
object.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(type(of: property).dynamicObjectType))'s value outside it's designated queue."
|
||||
)
|
||||
values[property.keyPath] = type(of: property).valueForSnapshot(
|
||||
field: property,
|
||||
for: object.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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<R, D>: 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<D2>(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<V: AllowedObjectiveCAttributeKeyPathValue>(dynamicMember member: KeyPath<Destination, V>) -> 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<V: NSManagedObject>(dynamicMember member: KeyPath<Destination, V>) -> 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<V: NSManagedObject>(dynamicMember member: KeyPath<Destination, V?>) -> 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<V: NSOrderedSet>(dynamicMember member: KeyPath<Destination, V>) -> 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<V: NSOrderedSet>(dynamicMember member: KeyPath<Destination, V?>) -> 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<V: NSSet>(dynamicMember member: KeyPath<Destination, V>) -> 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<V: NSSet>(dynamicMember member: KeyPath<Destination, V?>) -> 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<K: AttributeKeyPathStringConvertible>(dynamicMember member: KeyPath<Destination, K>) -> 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<K: RelationshipKeyPathStringConvertible>(dynamicMember member: KeyPath<Destination, K>) -> DynamicObjectMeta<(Root, Destination), K.DestinationValueType> {
|
||||
|
||||
let keyPathString = String(keyPath: member)
|
||||
return self.appending(keyPathString: keyPathString)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -68,89 +68,124 @@ extension DynamicSchema {
|
||||
for (attributeName, attribute) in attributesByName {
|
||||
|
||||
let containerType: String
|
||||
if attribute.attributeType == .transformableAttributeType {
|
||||
|
||||
if attribute.isOptional {
|
||||
|
||||
containerType = "Transformable.Optional"
|
||||
}
|
||||
else {
|
||||
|
||||
containerType = "Transformable.Required"
|
||||
}
|
||||
if attribute.isTransient || attribute.attributeType == .undefinedAttributeType {
|
||||
|
||||
containerType = "Field.Virtual"
|
||||
}
|
||||
else if attribute.attributeType == .transformableAttributeType {
|
||||
|
||||
containerType = "Field.Coded"
|
||||
}
|
||||
else {
|
||||
|
||||
if attribute.isOptional {
|
||||
|
||||
containerType = "Value.Optional"
|
||||
}
|
||||
else {
|
||||
|
||||
containerType = "Value.Required"
|
||||
}
|
||||
|
||||
containerType = "Field.Stored"
|
||||
}
|
||||
let valueType: Any.Type
|
||||
var valueTypeString: String
|
||||
var defaultString = ""
|
||||
var coderString = ""
|
||||
switch attribute.attributeType {
|
||||
|
||||
case .integer16AttributeType:
|
||||
valueType = Int16.self
|
||||
valueTypeString = String(describing: Int16.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Int16.QueryableNativeType?).flatMap(Int16.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: \(defaultValue)"
|
||||
defaultString = " = \(defaultValue)"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .integer32AttributeType:
|
||||
valueType = Int32.self
|
||||
valueTypeString = String(describing: Int32.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Int32.QueryableNativeType?).flatMap(Int32.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: \(defaultValue)"
|
||||
|
||||
defaultString = " = \(defaultValue)"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .integer64AttributeType:
|
||||
valueType = Int64.self
|
||||
valueTypeString = String(describing: Int64.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Int64.QueryableNativeType?).flatMap(Int64.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: \(defaultValue)"
|
||||
defaultString = " = \(defaultValue)"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .decimalAttributeType:
|
||||
valueType = NSDecimalNumber.self
|
||||
valueTypeString = String(describing: NSDecimalNumber.self)
|
||||
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber?) {
|
||||
|
||||
defaultString = ", initial: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
|
||||
defaultString = " = NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .doubleAttributeType:
|
||||
valueType = Double.self
|
||||
valueTypeString = String(describing: Double.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Double.QueryableNativeType?).flatMap(Double.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: \(defaultValue)"
|
||||
defaultString = " = \(defaultValue)"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .floatAttributeType:
|
||||
valueType = Float.self
|
||||
valueTypeString = String(describing: Float.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Float.QueryableNativeType?).flatMap(Float.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: \(defaultValue)"
|
||||
defaultString = " = \(defaultValue)"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .stringAttributeType:
|
||||
valueType = String.self
|
||||
valueTypeString = String(describing: String.self)
|
||||
if let defaultValue = (attribute.defaultValue as! String.QueryableNativeType?).flatMap(String.cs_fromQueryableNativeType) {
|
||||
|
||||
// TODO: escape strings
|
||||
defaultString = ", initial: \"\(defaultValue)\""
|
||||
|
||||
defaultString = " = \"\(defaultValue.replacingOccurrences(of: "\\", with: "\\\\"))\""
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .booleanAttributeType:
|
||||
valueType = Bool.self
|
||||
valueTypeString = String(describing: Bool.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Bool.QueryableNativeType?).flatMap(Bool.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: \(defaultValue ? "true" : "false")"
|
||||
defaultString = " = \(defaultValue ? "true" : "false")"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .dateAttributeType:
|
||||
valueType = Date.self
|
||||
valueTypeString = String(describing: Date.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Date.QueryableNativeType?).flatMap(Date.cs_fromQueryableNativeType) {
|
||||
|
||||
defaultString = ", initial: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
|
||||
defaultString = " = Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .binaryDataAttributeType:
|
||||
valueType = Data.self
|
||||
valueTypeString = String(describing: Data.self)
|
||||
if let defaultValue = (attribute.defaultValue as! Data.QueryableNativeType?).flatMap(Data.cs_fromQueryableNativeType) {
|
||||
|
||||
let bytes = defaultValue.withUnsafeBytes { (pointer) in
|
||||
@@ -158,46 +193,106 @@ extension DynamicSchema {
|
||||
.bindMemory(to: UInt64.self)
|
||||
.map({ "\("0x\(String($0, radix: 16, uppercase: false))")" })
|
||||
}
|
||||
defaultString = ", initial: Data(bytes: [\(bytes.joined(separator: ", "))])"
|
||||
defaultString = " = Data(bytes: [\(bytes.joined(separator: ", "))])"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
case .transformableAttributeType:
|
||||
if let valueTransformerName = attribute.valueTransformerName {
|
||||
|
||||
coderString = ", coder: /* Required compatible FieldCoderType implementation for ValueTransformer named \"\(valueTransformerName)\" */"
|
||||
}
|
||||
else {
|
||||
|
||||
coderString = ", coder: FieldCoders.NSCoding.self"
|
||||
}
|
||||
if let attributeValueClassName = attribute.attributeValueClassName {
|
||||
|
||||
valueType = NSClassFromString(attributeValueClassName)!
|
||||
|
||||
valueTypeString = String(describing: NSClassFromString(attributeValueClassName)!)
|
||||
}
|
||||
else {
|
||||
|
||||
valueTypeString = "/* <required> */"
|
||||
}
|
||||
if let defaultValue = attribute.defaultValue {
|
||||
|
||||
switch defaultValue {
|
||||
|
||||
case let defaultValueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
if let defaultValue = defaultValueBox.value {
|
||||
|
||||
defaultString = " = /* \"\(defaultValue)\" */"
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
defaultString = " = nil"
|
||||
}
|
||||
else {
|
||||
|
||||
defaultString = " = /* <required> */"
|
||||
}
|
||||
|
||||
case let defaultValue:
|
||||
defaultString = " = /* \"\(defaultValue)\" */"
|
||||
}
|
||||
}
|
||||
else if attribute.isOptional {
|
||||
|
||||
defaultString = " = nil"
|
||||
}
|
||||
else {
|
||||
|
||||
valueType = (NSCoding & NSCopying).self
|
||||
defaultString = " = /* <required> */"
|
||||
}
|
||||
if let defaultValue = attribute.defaultValue {
|
||||
|
||||
defaultString = ", initial: /* \"\(defaultValue)\" */"
|
||||
if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
}
|
||||
else if !attribute.isOptional {
|
||||
|
||||
defaultString = ", initial: /* required */"
|
||||
|
||||
case .undefinedAttributeType where attribute.isTransient:
|
||||
coderString = ", customGetter: \\* <required> *\\"
|
||||
if let attributeValueClassName = attribute.attributeValueClassName {
|
||||
|
||||
valueTypeString = String(describing: NSClassFromString(attributeValueClassName)!)
|
||||
}
|
||||
else {
|
||||
|
||||
valueTypeString = " = /* <required> */"
|
||||
}
|
||||
if attribute.isOptional {
|
||||
|
||||
valueTypeString += "?"
|
||||
defaultString = " = nil"
|
||||
}
|
||||
|
||||
default:
|
||||
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")
|
||||
}
|
||||
let transientString = attribute.isTransient ? ", isTransient: true" : ""
|
||||
// TODO: escape strings
|
||||
let versionHashModifierString = attribute.versionHashModifier
|
||||
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
|
||||
// TODO: escape strings
|
||||
.map({ ", versionHashModifier: \"\($0)\"" }) ?? ""
|
||||
|
||||
let renamingIdentifierString = attribute.renamingIdentifier
|
||||
.flatMap({ ($0 == attributeName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? ""
|
||||
output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n")
|
||||
.map({ ($0 == attributeName ? "" : ", previousVersionKeyPath: \"\($0)\"") }) ?? ""
|
||||
if attributeName.hasPrefix("_") {
|
||||
|
||||
output.append(" #warning(\"Field variable names cannot start with underscores)")
|
||||
}
|
||||
output.append(" @\(containerType)(\"\(attributeName)\"\(versionHashModifierString)\(renamingIdentifierString)\(coderString))\n")
|
||||
output.append(" var \(attributeName): \(valueTypeString)\(defaultString)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
let relationshipsByName = entity.relationshipsByName
|
||||
if !relationshipsByName.isEmpty {
|
||||
|
||||
|
||||
output.append(" \n")
|
||||
for (relationshipName, relationship) in relationshipsByName {
|
||||
|
||||
let containerType: String
|
||||
let destinationEntityName = relationship.destinationEntity!.name!
|
||||
var minCountString = ""
|
||||
var maxCountString = ""
|
||||
if relationship.isToMany {
|
||||
@@ -206,11 +301,11 @@ extension DynamicSchema {
|
||||
let maxCount = relationship.maxCount
|
||||
if relationship.isOrdered {
|
||||
|
||||
containerType = "Relationship.ToManyOrdered"
|
||||
containerType = "[\(destinationEntityName)]"
|
||||
}
|
||||
else {
|
||||
|
||||
containerType = "Relationship.ToManyUnordered"
|
||||
containerType = "Set<\(destinationEntityName)>"
|
||||
}
|
||||
if minCount > 0 {
|
||||
|
||||
@@ -222,16 +317,16 @@ extension DynamicSchema {
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
containerType = "Relationship.ToOne"
|
||||
|
||||
containerType = "\(destinationEntityName)?"
|
||||
}
|
||||
var inverseString = ""
|
||||
let relationshipQualifier = "\(entityName).\(relationshipName)"
|
||||
if !addedInverse.contains(relationshipQualifier),
|
||||
let inverseRelationship = relationship.inverseRelationship {
|
||||
|
||||
inverseString = ", inverse: { $0.\(inverseRelationship.name) }"
|
||||
addedInverse.insert("\(relationship.destinationEntity!.name!).\(inverseRelationship.name)")
|
||||
inverseString = ", inverse: \\.$\(inverseRelationship.name)"
|
||||
addedInverse.insert("\(destinationEntityName).\(inverseRelationship.name)")
|
||||
}
|
||||
var deleteRuleString = ""
|
||||
if relationship.deleteRule != .nullifyDeleteRule {
|
||||
@@ -252,10 +347,15 @@ extension DynamicSchema {
|
||||
}
|
||||
}
|
||||
let versionHashModifierString = relationship.versionHashModifier
|
||||
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
|
||||
.map({ ", versionHashModifier: \"\($0)\"" }) ?? ""
|
||||
let renamingIdentifierString = relationship.renamingIdentifier
|
||||
.flatMap({ ($0 == relationshipName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? ""
|
||||
output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n")
|
||||
.map({ ($0 == relationshipName ? "" : ", previousVersionKeyPath: \"\($0)\"") }) ?? ""
|
||||
if relationshipName.hasPrefix("_") {
|
||||
|
||||
output.append(" #error(\"Field variable names cannot start with underscores)\n")
|
||||
}
|
||||
output.append(" @Field.Relationship(\"\(relationshipName)\"\(minCountString)\(maxCountString)\(inverseString)\(deleteRuleString)\(versionHashModifierString)\(renamingIdentifierString))\n")
|
||||
output.append(" var \(relationshipName): \(containerType)\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
232
Sources/FIeldRelationshipType.swift
Normal file
232
Sources/FIeldRelationshipType.swift
Normal file
@@ -0,0 +1,232 @@
|
||||
//
|
||||
// FIeldRelationshipType.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: - FieldRelationshipType
|
||||
|
||||
/**
|
||||
Values to be used for `Field.Relationship` properties.
|
||||
*/
|
||||
public protocol FieldRelationshipType {
|
||||
|
||||
/**
|
||||
The destination object's type
|
||||
*/
|
||||
associatedtype DestinationObjectType: CoreStoreObject
|
||||
|
||||
/**
|
||||
The Objective-C native type synthesized by Core Data
|
||||
*/
|
||||
associatedtype NativeValueType: AnyObject
|
||||
|
||||
/**
|
||||
The corresponding value for this field returned from `ObjectSnapshot` properties.
|
||||
*/
|
||||
associatedtype SnapshotValueType
|
||||
|
||||
/**
|
||||
The corresponding value for this field returned from `ObjectPublisher` properties.
|
||||
*/
|
||||
associatedtype PublishedType
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
static func cs_toReturnType(from value: NativeValueType?) -> Self
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
static func cs_toPublishedType(from value: SnapshotValueType, in context: NSManagedObjectContext) -> PublishedType
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
static func cs_toNativeType(from value: Self) -> NativeValueType?
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
static func cs_toSnapshotType(from value: PublishedType) -> SnapshotValueType
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
static func cs_valueForSnapshot(from objectIDs: [DestinationObjectType.ObjectID]) -> SnapshotValueType
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldRelationshipToOneType: FieldRelationshipType
|
||||
|
||||
public protocol FieldRelationshipToOneType: FieldRelationshipType {}
|
||||
|
||||
|
||||
// MARK: - FieldRelationshipToManyType: FieldRelationshipType where Self: Sequence
|
||||
|
||||
public protocol FieldRelationshipToManyType: FieldRelationshipType where Self: Sequence {}
|
||||
|
||||
|
||||
// MARK: - FieldRelationshipToManyOrderedType: FieldRelationshipToManyType
|
||||
|
||||
public protocol FieldRelationshipToManyOrderedType: FieldRelationshipToManyType {}
|
||||
|
||||
|
||||
// MARK: - FieldRelationshipToManyUnorderedType: FieldRelationshipToManyType
|
||||
|
||||
public protocol FieldRelationshipToManyUnorderedType: FieldRelationshipToManyType {}
|
||||
|
||||
|
||||
// MARK: - Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrapped: CoreStoreObject
|
||||
|
||||
extension Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrapped: CoreStoreObject {
|
||||
|
||||
// MARK: FieldRelationshipType
|
||||
|
||||
public typealias DestinationObjectType = Wrapped
|
||||
|
||||
public typealias NativeValueType = NSManagedObject
|
||||
|
||||
public typealias SnapshotValueType = NSManagedObjectID?
|
||||
|
||||
public typealias PublishedType = ObjectPublisher<DestinationObjectType>?
|
||||
|
||||
public static func cs_toReturnType(from value: NativeValueType?) -> Self {
|
||||
|
||||
return value.map(Wrapped.cs_fromRaw(object:))
|
||||
}
|
||||
|
||||
public static func cs_toPublishedType(from value: SnapshotValueType, in context: NSManagedObjectContext) -> PublishedType {
|
||||
|
||||
return value.map(context.objectPublisher(objectID:))
|
||||
}
|
||||
|
||||
public static func cs_toNativeType(from value: Self) -> NativeValueType? {
|
||||
|
||||
return value?.cs_toRaw()
|
||||
}
|
||||
|
||||
public static func cs_toSnapshotType(from value: PublishedType) -> SnapshotValueType {
|
||||
|
||||
return value?.objectID()
|
||||
}
|
||||
|
||||
public static func cs_valueForSnapshot(from objectIDs: [DestinationObjectType.ObjectID]) -> SnapshotValueType {
|
||||
|
||||
return objectIDs.first
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelationshipToManyOrderedType where Element: CoreStoreObject
|
||||
|
||||
extension Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelationshipToManyOrderedType where Element: CoreStoreObject {
|
||||
|
||||
// MARK: FieldRelationshipType
|
||||
|
||||
public typealias DestinationObjectType = Element
|
||||
|
||||
public typealias NativeValueType = NSOrderedSet
|
||||
|
||||
public typealias SnapshotValueType = [NSManagedObjectID]
|
||||
|
||||
public typealias PublishedType = [ObjectPublisher<DestinationObjectType>]
|
||||
|
||||
public static func cs_toReturnType(from value: NativeValueType?) -> Self {
|
||||
|
||||
guard let value = value else {
|
||||
|
||||
return []
|
||||
}
|
||||
return value.map({ Element.cs_fromRaw(object: $0 as! NSManagedObject) })
|
||||
}
|
||||
|
||||
public static func cs_toPublishedType(from value: SnapshotValueType, in context: NSManagedObjectContext) -> PublishedType {
|
||||
|
||||
return value.map(context.objectPublisher(objectID:))
|
||||
}
|
||||
|
||||
public static func cs_toNativeType(from value: Self) -> NativeValueType? {
|
||||
|
||||
return NSOrderedSet(array: value.map({ $0.rawObject! }))
|
||||
}
|
||||
|
||||
public static func cs_toSnapshotType(from value: PublishedType) -> SnapshotValueType {
|
||||
|
||||
return value.map({ $0.objectID() })
|
||||
}
|
||||
|
||||
public static func cs_valueForSnapshot(from objectIDs: [DestinationObjectType.ObjectID]) -> SnapshotValueType {
|
||||
|
||||
return objectIDs
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelationshipToManyUnorderedType where Element: CoreStoreObject
|
||||
|
||||
extension Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelationshipToManyUnorderedType where Element: CoreStoreObject {
|
||||
|
||||
// MARK: FieldRelationshipType
|
||||
|
||||
public typealias DestinationObjectType = Element
|
||||
|
||||
public typealias NativeValueType = NSSet
|
||||
|
||||
public typealias SnapshotValueType = Set<NSManagedObjectID>
|
||||
|
||||
public typealias PublishedType = Set<ObjectPublisher<DestinationObjectType>>
|
||||
|
||||
public static func cs_toReturnType(from value: NativeValueType?) -> Self {
|
||||
|
||||
guard let value = value else {
|
||||
|
||||
return []
|
||||
}
|
||||
return Set(value.map({ Element.cs_fromRaw(object: $0 as! NSManagedObject) }))
|
||||
}
|
||||
|
||||
public static func cs_toPublishedType(from value: SnapshotValueType, in context: NSManagedObjectContext) -> PublishedType {
|
||||
|
||||
return PublishedType(value.map(context.objectPublisher(objectID:)))
|
||||
}
|
||||
|
||||
public static func cs_toNativeType(from value: Self) -> NativeValueType? {
|
||||
|
||||
return NSSet(array: value.map({ $0.rawObject! }))
|
||||
}
|
||||
|
||||
public static func cs_toSnapshotType(from value: PublishedType) -> SnapshotValueType {
|
||||
|
||||
return SnapshotValueType(value.map({ $0.objectID() }))
|
||||
}
|
||||
|
||||
public static func cs_valueForSnapshot(from objectIDs: [DestinationObjectType.ObjectID]) -> SnapshotValueType {
|
||||
|
||||
return .init(objectIDs)
|
||||
}
|
||||
}
|
||||
547
Sources/Field.Coded.swift
Normal file
547
Sources/Field.Coded.swift
Normal file
@@ -0,0 +1,547 @@
|
||||
//
|
||||
// Field.Coded.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - FieldContainer
|
||||
|
||||
extension FieldContainer {
|
||||
|
||||
// MARK: - Coded
|
||||
|
||||
/**
|
||||
The containing type for stored property values. Any type supported by the specified encoder/decoder are allowed.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
|
||||
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self)
|
||||
var eyeColor: UIColor = .black
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Stored(...) var` syntax will be ignored.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Coded<V>: AttributeKeyPathStringConvertible, FieldAttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self)
|
||||
var eyeColor: UIColor = .black
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter coder: The `FieldCoderType` to be used for encoding and decoding the value
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, 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<Coder: FieldCoderType>(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
coder fieldCoderType: Coder.Type,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) where Coder.FieldStoredValue == V {
|
||||
|
||||
self.init(
|
||||
defaultValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(fieldCoderType) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self)
|
||||
var eyeColor: UIColor = .black
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter coder: The closures to be used for encoding and decoding the value
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
coder: (encode: (V) -> Data?, decode: (Data?) -> V),
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
defaultValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: @propertyWrapper
|
||||
|
||||
@available(*, unavailable)
|
||||
public var wrappedValue: V {
|
||||
|
||||
get { fatalError() }
|
||||
set { fatalError() }
|
||||
}
|
||||
|
||||
public var projectedValue: Self {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
public static subscript(
|
||||
_enclosingInstance instance: O,
|
||||
wrapped wrappedKeyPath: ReferenceWritableKeyPath<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> 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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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 var dynamicObjectType: CoreStoreObject.Type {
|
||||
|
||||
return ObjectType.self
|
||||
}
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
let field = field as! Self
|
||||
if let customGetter = field.customGetter {
|
||||
|
||||
return customGetter(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field)
|
||||
)
|
||||
}
|
||||
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.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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field),
|
||||
newValue
|
||||
)
|
||||
}
|
||||
return rawObject.setValue(newValue, forKey: keyPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FieldAttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> FieldAttributeProtocol.EntityDescriptionValues
|
||||
|
||||
internal var getter: CoreStoreManagedObject.CustomGetter? {
|
||||
|
||||
let keyPath = self.keyPath
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
switch rawObject.primitiveValue(forKey: keyPath) {
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
rawObject.setPrimitiveValue(valueBox.value, forKey: keyPath)
|
||||
return valueBox.value
|
||||
|
||||
case let value:
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self)
|
||||
)
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
internal var setter: CoreStoreManagedObject.CustomSetter? {
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self),
|
||||
newValue as! V
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
defaultValue: @escaping () -> Any?,
|
||||
keyPath: KeyPathString,
|
||||
isOptional: Bool,
|
||||
versionHashModifier: @escaping () -> String?,
|
||||
renamingIdentifier: @escaping () -> String?,
|
||||
valueTransformer: @escaping () -> Internals.AnyFieldCoder?,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
|
||||
let fieldCoder = valueTransformer()
|
||||
return (
|
||||
attributeType: .transformableAttributeType,
|
||||
isOptional: isOptional,
|
||||
isTransient: false,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
valueTransformer: fieldCoder,
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: Internals.AnyFieldCoder.TransformableDefaultValueCodingBox(
|
||||
defaultValue: defaultValue(),
|
||||
fieldCoder: fieldCoder
|
||||
)
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?
|
||||
private let customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Coded where V: FieldOptionalType
|
||||
|
||||
extension FieldContainer.Coded where V: FieldOptionalType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self)
|
||||
var eyeColor: UIColor? = nil
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter coder: The `FieldCoderType` to be used for encoding and decoding the value
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, 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<Coder: FieldCoderType>(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
coder: Coder.Type,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) where Coder.FieldStoredValue == V.Wrapped {
|
||||
|
||||
self.init(
|
||||
defaultValue: { initial().cs_wrappedValue },
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(coder) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("eyeColor", coder: FieldCoders.NSCoding.self)
|
||||
var eyeColor: UIColor? = nil
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter coder: The closures to be used for encoding and decoding the value
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
coder: (encode: (V) -> Data?, decode: (Data?) -> V),
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
defaultValue: { initial().cs_wrappedValue },
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(tag: UUID(), encode: coder.encode, decode: coder.decode) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Coded where V: DefaultNSSecureCodable
|
||||
|
||||
extension FieldContainer.Coded where V: DefaultNSSecureCodable {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("customInfo")
|
||||
var customInfo: NSDictionary = [:]
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
defaultValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding<V>.self) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable
|
||||
|
||||
extension FieldContainer.Coded where V: FieldOptionalType, V.Wrapped: DefaultNSSecureCodable {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property. This overload is for types supported by Core Data's default NSSecureCodable implementation: `NSArray`, `NSDictionary`, `NSSet`, `NSString`, `NSNumber`, `NSDate`, `NSData`, `NSURL`, `NSUUID`, and `NSNull`.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("customInfo")
|
||||
var customInfo: NSDictionary? = nil
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
defaultValue: { initial().cs_wrappedValue },
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
valueTransformer: { Internals.AnyFieldCoder(FieldCoders.DefaultNSSecureCoding<V.Wrapped>.self) },
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
585
Sources/Field.Relationship.swift
Normal file
585
Sources/Field.Relationship.swift
Normal file
@@ -0,0 +1,585 @@
|
||||
//
|
||||
// Field.ToOne.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - FieldContainer
|
||||
|
||||
extension FieldContainer {
|
||||
|
||||
// MARK: - Relationship
|
||||
|
||||
/**
|
||||
The containing type for relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Dog>
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Relationship(...) var` syntax will be ignored.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Relationship<V: FieldRelationshipType>: RelationshipKeyPathStringConvertible, FieldRelationshipProtocol {
|
||||
|
||||
/**
|
||||
Overload for compiler error message only
|
||||
*/
|
||||
@available(*, unavailable, message: "Field.Relationship properties are not allowed to have initial values, including `nil`.")
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil
|
||||
) {
|
||||
|
||||
fatalError()
|
||||
}
|
||||
|
||||
/**
|
||||
Overload for compiler error message only
|
||||
*/
|
||||
@available(*, unavailable, message: "Field.Relationship properties are not allowed to have initial values, including `nil`.")
|
||||
public init<D>(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil
|
||||
) {
|
||||
|
||||
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<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> 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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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.DestinationObjectType
|
||||
|
||||
|
||||
// MARK: RelationshipKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = V
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: FieldProtocol
|
||||
|
||||
internal static var dynamicObjectType: CoreStoreObject.Type {
|
||||
|
||||
return ObjectType.self
|
||||
}
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
let field = field as! Self
|
||||
let keyPath = field.keyPath
|
||||
return V.cs_toReturnType(
|
||||
from: rawObject.value(forKey: keyPath) as! V.NativeValueType?
|
||||
)
|
||||
}
|
||||
|
||||
internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) {
|
||||
|
||||
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
|
||||
return rawObject.setValue(
|
||||
V.cs_toNativeType(from: newValue),
|
||||
forKey: keyPath
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: FieldRelationshipProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> FieldRelationshipProtocol.EntityDescriptionValues
|
||||
|
||||
internal static func valueForSnapshot(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
|
||||
return V.cs_valueForSnapshot(from: rawObject.objectIDs(forRelationshipNamed: field.keyPath))
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
keyPath: KeyPathString,
|
||||
isToMany: Bool,
|
||||
isOrdered: Bool,
|
||||
deleteRule: DeleteRule,
|
||||
inverseKeyPath: @escaping () -> KeyPathString?,
|
||||
versionHashModifier: @escaping () -> String?,
|
||||
renamingIdentifier: @escaping () -> String?,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>,
|
||||
minCount: Int,
|
||||
maxCount: Int
|
||||
) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
|
||||
let range = (Swift.max(0, minCount) ... maxCount)
|
||||
return (
|
||||
isToMany: isToMany,
|
||||
isOrdered: isOrdered,
|
||||
deleteRule: deleteRule.nativeValue,
|
||||
inverse: (type: V.DestinationObjectType.self, keyPath: inverseKeyPath()),
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
minCount: range.lowerBound,
|
||||
maxCount: range.upperBound
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DeleteRule
|
||||
|
||||
/**
|
||||
These constants define what happens to relationships when an object is deleted.
|
||||
*/
|
||||
public enum DeleteRule {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
If the object is deleted, back pointers from the objects to which it is related are nullified.
|
||||
*/
|
||||
case nullify
|
||||
|
||||
/**
|
||||
If the object is deleted, the destination object or objects of this relationship are also deleted.
|
||||
*/
|
||||
case cascade
|
||||
|
||||
/**
|
||||
If the destination of this relationship is not nil, the delete creates a validation error.
|
||||
*/
|
||||
case deny
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var nativeValue: NSDeleteRule {
|
||||
|
||||
switch self {
|
||||
|
||||
case .nullify: return .nullifyDeleteRule
|
||||
case .cascade: return .cascadeDeleteRule
|
||||
case .deny: return .denyDeleteRule
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Relationship where V: FieldRelationshipToOneType
|
||||
|
||||
extension FieldContainer.Relationship where V: FieldRelationshipToOneType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Dog>
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter 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,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isToMany: false,
|
||||
isOrdered: false,
|
||||
deleteRule: deleteRule,
|
||||
inverseKeyPath: { nil },
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
affectedByKeyPaths: affectedByKeyPaths,
|
||||
minCount: 0,
|
||||
maxCount: 1
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Dog>
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter 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<D>(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) where D: FieldRelationshipType {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isToMany: false,
|
||||
isOrdered: false,
|
||||
deleteRule: deleteRule,
|
||||
inverseKeyPath: { V.DestinationObjectType.meta[keyPath: inverse].keyPath },
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
affectedByKeyPaths: affectedByKeyPaths,
|
||||
minCount: 0,
|
||||
maxCount: 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Relationship: ToManyRelationshipKeyPathStringConvertible where V: FieldRelationshipToManyType
|
||||
|
||||
extension FieldContainer.Relationship: ToManyRelationshipKeyPathStringConvertible where V: FieldRelationshipToManyType {}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Relationship where V: FieldRelationshipToManyOrderedType
|
||||
|
||||
extension FieldContainer.Relationship where V: FieldRelationshipToManyOrderedType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Array<Dog>
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter 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,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isToMany: true,
|
||||
isOrdered: true,
|
||||
deleteRule: deleteRule,
|
||||
inverseKeyPath: { nil },
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
affectedByKeyPaths: affectedByKeyPaths,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Array<Dog>
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter 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<D>(
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) where D: FieldRelationshipType {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isToMany: true,
|
||||
isOrdered: true,
|
||||
deleteRule: deleteRule,
|
||||
inverseKeyPath: { V.DestinationObjectType.meta[keyPath: inverse].keyPath },
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
affectedByKeyPaths: affectedByKeyPaths,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Relationship where V: FieldRelationshipToManyUnorderedType
|
||||
|
||||
extension FieldContainer.Relationship where V: FieldRelationshipToManyUnorderedType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Dog>
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter 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,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isToMany: true,
|
||||
isOrdered: false,
|
||||
deleteRule: deleteRule,
|
||||
inverseKeyPath: { nil },
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
affectedByKeyPaths: affectedByKeyPaths,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("master")
|
||||
var master: Person?
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Dog>
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter 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<D>(
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
inverse: KeyPath<V.DestinationObjectType, FieldContainer<V.DestinationObjectType>.Relationship<D>>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) where D: FieldRelationshipType {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
isToMany: true,
|
||||
isOrdered: false,
|
||||
deleteRule: deleteRule,
|
||||
inverseKeyPath: { V.DestinationObjectType.meta[keyPath: inverse].keyPath },
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
affectedByKeyPaths: affectedByKeyPaths,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount
|
||||
)
|
||||
}
|
||||
}
|
||||
347
Sources/Field.Stored.swift
Normal file
347
Sources/Field.Stored.swift
Normal file
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// 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("title")
|
||||
var title: String = "Mr."
|
||||
|
||||
@Field.Stored("nickname")
|
||||
var nickname: String?
|
||||
}
|
||||
```
|
||||
- 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<V: FieldStorableType>: AttributeKeyPathStringConvertible, FieldAttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Stored("title")
|
||||
var title: String = "Mr."
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []
|
||||
) {
|
||||
|
||||
self.init(
|
||||
wrappedValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: false,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
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<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> 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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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 var dynamicObjectType: CoreStoreObject.Type {
|
||||
|
||||
return ObjectType.self
|
||||
}
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
let field = field as! Self
|
||||
if let customGetter = field.customGetter {
|
||||
|
||||
return customGetter(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field)
|
||||
)
|
||||
}
|
||||
let keyPath = field.keyPath
|
||||
switch rawObject.value(forKey: keyPath) {
|
||||
|
||||
case let rawValue as V.FieldStoredNativeType:
|
||||
return V.cs_fromFieldStoredNativeType(rawValue)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?) {
|
||||
|
||||
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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field),
|
||||
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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self)
|
||||
)
|
||||
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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self),
|
||||
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: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: V.cs_rawAttributeType,
|
||||
isOptional: isOptional,
|
||||
isTransient: false,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
valueTransformer: nil,
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial().cs_toFieldStoredNativeType()
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?
|
||||
private let customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ 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("nickname")
|
||||
var nickname: String?
|
||||
}
|
||||
```
|
||||
- parameter initial: the initial value for the property when the object is first created.
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter previousVersionKeyPath: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property's `keyPath` with a matching destination entity property's `previousVersionKeyPath` indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's `keyPath`.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V = nil,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
previousVersionKeyPath: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
self.init(
|
||||
wrappedValue: initial,
|
||||
keyPath: keyPath,
|
||||
isOptional: true,
|
||||
versionHashModifier: versionHashModifier,
|
||||
renamingIdentifier: previousVersionKeyPath,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
354
Sources/Field.Virtual.swift
Normal file
354
Sources/Field.Virtual.swift
Normal file
@@ -0,0 +1,354 @@
|
||||
//
|
||||
// Field.Virtual.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: - Virtual
|
||||
|
||||
/**
|
||||
The containing type for computed property values. Because this value is not persisted to the backing store, any type is supported. `Field.Virtual` properties are not allowed to have initial values, including `nil` for optional types.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
|
||||
@Field.Virtual(
|
||||
"pluralName",
|
||||
customGetter: { (object, field) in
|
||||
return object.$species.value + "s"
|
||||
}
|
||||
)
|
||||
var pluralName: String
|
||||
|
||||
@Field.Stored("species")
|
||||
var species: String = ""
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.Virtual(...) var` syntax will be ignored.
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Virtual<V>: AttributeKeyPathStringConvertible, FieldAttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property. `Field.Virtual` properties are not allowed to have initial values, including `nil` for optional types.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Virtual(
|
||||
"pluralName",
|
||||
customGetter: { (object, field) in
|
||||
return object.$species.value + "s"
|
||||
}
|
||||
)
|
||||
var pluralName: String
|
||||
|
||||
@Field.Stored("species")
|
||||
var species: String = ""
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, 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,
|
||||
customGetter: @escaping (_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Overload for compiler error message only
|
||||
*/
|
||||
@available(*, unavailable, message: "Field.Virtual properties are not allowed to have initial values, including `nil`.")
|
||||
public init(
|
||||
wrappedValue initial: @autoclosure @escaping () -> V,
|
||||
_ keyPath: KeyPathString,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
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<O, V>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<O, Self>
|
||||
) -> 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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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."
|
||||
)
|
||||
Internals.assert(
|
||||
instance.rawObject?.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
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 var dynamicObjectType: CoreStoreObject.Type {
|
||||
|
||||
return ObjectType.self
|
||||
}
|
||||
|
||||
internal static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any? {
|
||||
|
||||
let field = field as! Self
|
||||
if let customGetter = field.customGetter {
|
||||
|
||||
return customGetter(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field)
|
||||
)
|
||||
}
|
||||
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.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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: field),
|
||||
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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
ObjectProxy<O>(rawObject),
|
||||
ObjectProxy<O>.FieldProxy<V>(rawObject: rawObject, field: self),
|
||||
newValue as! V
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
keyPath: KeyPathString,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? ,
|
||||
affectedByKeyPaths: @escaping () -> Set<KeyPathString>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: .undefinedAttributeType,
|
||||
isOptional: true,
|
||||
isTransient: true,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: nil,
|
||||
renamingIdentifier: nil,
|
||||
valueTransformer: nil,
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: nil
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)?
|
||||
private let customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer.Virtual where V: FieldOptionalType
|
||||
|
||||
extension FieldContainer.Virtual where V: FieldOptionalType {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property. `Field.Virtual` properties are not allowed to have initial values, including `nil` for optional types.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Virtual(
|
||||
"pluralName",
|
||||
customGetter: { (object, field) in
|
||||
return object.$species.value + "s"
|
||||
}
|
||||
)
|
||||
var pluralName: String?
|
||||
|
||||
@Field.Stored("species")
|
||||
var species: String = ""
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `ObjectProxy<O>`, which acts as a type-safe proxy for the receiver. When accessing the property value from `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, which would unintentionally execute the same closure again recursively. Do not make assumptions on the thread/queue that the closure is executed on; accessors may be called from `NSError` logs for example.
|
||||
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `ObjectProxy<O>`, 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 `ObjectProxy<O>`, make sure to use `field.primitiveValue` instead of `field.value`, 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,
|
||||
customGetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>) -> V)? = nil,
|
||||
customSetter: ((_ object: ObjectProxy<O>, _ field: ObjectProxy<O>.FieldProxy<V>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<KeyPathString> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
customGetter: customGetter,
|
||||
customSetter: customSetter,
|
||||
affectedByKeyPaths: affectedByKeyPaths
|
||||
)
|
||||
}
|
||||
}
|
||||
91
Sources/Field.swift
Normal file
91
Sources/Field.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// 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.
|
||||
```
|
||||
class Pet: CoreStoreObject {
|
||||
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Stored("nickname")
|
||||
var nickname: String?
|
||||
|
||||
@Field.Coded("color", coder: FieldCoders.Plist.self)
|
||||
var eyeColor: UIColor?
|
||||
|
||||
@Field.Relationship("owner", inverse: \.$pets)
|
||||
var owner: Person?
|
||||
|
||||
@Field.Relationship("children")
|
||||
var children: Array<Pet>
|
||||
|
||||
@Field.Relationship("parents", inverse: \.$children)
|
||||
var parents: Set<Pet>
|
||||
}
|
||||
```
|
||||
- Important: `Field` properties are required to be used as `@propertyWrapper`s. Any other declaration not using the `@Field.*(...) var` syntax will result in undefined behavior.
|
||||
*/
|
||||
public typealias Field = FieldContainer<Self>
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldContainer
|
||||
|
||||
/**
|
||||
The containing type for value properties. Use the `Field` typealias instead for shorter syntax.
|
||||
```
|
||||
class Pet: CoreStoreObject {
|
||||
|
||||
@Field.Stored("species")
|
||||
var species = ""
|
||||
|
||||
@Field.Stored("nickname")
|
||||
var nickname: String?
|
||||
|
||||
@Field.Coded("color", coder: FieldCoders.Plist.self)
|
||||
var eyeColor: UIColor?
|
||||
|
||||
@Field.Relationship("owner", inverse: \.$pets)
|
||||
var owner: Person?
|
||||
|
||||
@Field.Relationship("children")
|
||||
var children: Array<Pet>
|
||||
|
||||
@Field.Relationship("parents", inverse: \.$children)
|
||||
var parents: Set<Pet>
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum FieldContainer<O: CoreStoreObject> {}
|
||||
51
Sources/FieldAttributeProtocol.swift
Normal file
51
Sources/FieldAttributeProtocol.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// FieldAttributeProtocol.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - FieldAttributeProtocol
|
||||
|
||||
internal protocol FieldAttributeProtocol: FieldProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
attributeType: NSAttributeType,
|
||||
isOptional: Bool,
|
||||
isTransient: Bool,
|
||||
allowsExternalBinaryDataStorage: Bool,
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
valueTransformer: Internals.AnyFieldCoder?,
|
||||
affectedByKeyPaths: Set<KeyPathString>,
|
||||
defaultValue: Any?
|
||||
)
|
||||
|
||||
static var dynamicObjectType: CoreStoreObject.Type { get }
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
var getter: CoreStoreManagedObject.CustomGetter? { get }
|
||||
var setter: CoreStoreManagedObject.CustomSetter? { get }
|
||||
}
|
||||
58
Sources/FieldCoderType.swift
Normal file
58
Sources/FieldCoderType.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// FieldCoderType.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - FieldCoderType
|
||||
|
||||
/**
|
||||
Types that implement encoding to and decoding from `Data` to be used in `Field.Coded` properties' `coder:` argument.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
@Field.Coded("profile", coder: FieldCoders.Json.self)
|
||||
var profile: Profile = .init()
|
||||
}
|
||||
```
|
||||
*/
|
||||
public protocol FieldCoderType {
|
||||
|
||||
/**
|
||||
The type to encode to and decode from `Data`
|
||||
*/
|
||||
associatedtype FieldStoredValue
|
||||
|
||||
/**
|
||||
Encodes the value to `Data`
|
||||
*/
|
||||
static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data?
|
||||
|
||||
/**
|
||||
Decodes the value from `Data`
|
||||
*/
|
||||
static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue?
|
||||
}
|
||||
95
Sources/FieldCoders.DefaultNSSecureCoding.swift
Normal file
95
Sources/FieldCoders.DefaultNSSecureCoding.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// FieldCoders.DefaultNSSecureCoding.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
/**
|
||||
A `FieldCoderType` that implements the default Core Data transformable attribute behavior, which uses a `ValueTransformer` named `.secureUnarchiveFromDataTransformerName`.
|
||||
*/
|
||||
public struct DefaultNSSecureCoding<T: DefaultNSSecureCodable>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = T
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
return ValueTransformer(forName: self.transformerName)?.reverseTransformedValue(fieldValue) as? Data
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
return ValueTransformer(forName: self.transformerName)?.transformedValue(data) as! FieldStoredValue?
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var transformerName: NSValueTransformerName {
|
||||
|
||||
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
|
||||
|
||||
return .secureUnarchiveFromDataTransformerName
|
||||
}
|
||||
else {
|
||||
|
||||
return .keyedUnarchiveFromDataTransformerName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DefaultNSSecureCodable
|
||||
|
||||
/**
|
||||
Types that are supported by `FieldCoders.DefaultNSSecureCoding`
|
||||
*/
|
||||
public protocol DefaultNSSecureCodable: NSObject, NSSecureCoding {}
|
||||
|
||||
extension NSArray: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSDictionary: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSSet: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSString: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSNumber: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSDate: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSData: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSURL: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSUUID: DefaultNSSecureCodable {}
|
||||
|
||||
extension NSNull: DefaultNSSecureCodable {}
|
||||
63
Sources/FieldCoders.Json.swift
Normal file
63
Sources/FieldCoders.Json.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// FieldCoders.Json.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
// MARK: - Json
|
||||
|
||||
/**
|
||||
A `FieldCoderType` that implements JSON encoding and decoding of `Codable` values.
|
||||
- Important: Due to restrictions of `JSONEncoder`, the value will be contained in single-item array before encoding.
|
||||
*/
|
||||
public struct Json<V: Codable>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = V
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
guard let fieldValue = fieldValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try! JSONEncoder().encode([fieldValue])
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try! JSONDecoder().decode([FieldStoredValue].self, from: data).first
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Sources/FieldCoders.NSCoding.swift
Normal file
94
Sources/FieldCoders.NSCoding.swift
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// FieldCoders.NSCoding.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
// MARK: - NSCoding
|
||||
|
||||
/**
|
||||
A `FieldCoderType` that implements encoding and decoding of `NSCoding` values
|
||||
*/
|
||||
public struct NSCoding<V: Foundation.NSObject & Foundation.NSCoding>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = V
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
guard let fieldValue = fieldValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
return try! NSKeyedArchiver.archivedData(
|
||||
withRootObject: fieldValue,
|
||||
requiringSecureCoding: self.requiresSecureCoding
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
return NSKeyedArchiver.archivedData(withRootObject: fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
return try! NSKeyedUnarchiver.unarchivedObject(ofClass: FieldStoredValue.self, from: data)
|
||||
}
|
||||
else {
|
||||
|
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as! FieldStoredValue?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private static var requiresSecureCoding: Bool {
|
||||
|
||||
switch FieldStoredValue.self {
|
||||
|
||||
case let valueType as NSSecureCoding.Type:
|
||||
return valueType.supportsSecureCoding
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Sources/FieldCoders.Plist.swift
Normal file
63
Sources/FieldCoders.Plist.swift
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// FieldCoders.Plist.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - FieldCoders
|
||||
|
||||
extension FieldCoders {
|
||||
|
||||
// MARK: - Plist
|
||||
|
||||
/**
|
||||
A `FieldCoderType` that implements Binary-Plist encoding and decoding of `Codable` values.
|
||||
- Important: Due to restrictions of `JSONEncoder`, the value will be contained in single-item array before encoding.
|
||||
*/
|
||||
public struct Plist<V: Codable>: FieldCoderType {
|
||||
|
||||
// MARK: FieldCoderType
|
||||
|
||||
public typealias FieldStoredValue = V
|
||||
|
||||
public static func encodeToStoredData(_ fieldValue: FieldStoredValue?) -> Data? {
|
||||
|
||||
guard let fieldValue = fieldValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try! PropertyListEncoder().encode([fieldValue])
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
|
||||
guard let data = data else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return try! PropertyListDecoder().decode([FieldStoredValue].self, from: data).first
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Sources/FieldCoders.swift
Normal file
30
Sources/FieldCoders.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// FieldCoders.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
|
||||
/**
|
||||
Namespace for Built-in Field Coders
|
||||
*/
|
||||
public enum FieldCoders {}
|
||||
59
Sources/FieldOptionalType.swift
Normal file
59
Sources/FieldOptionalType.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/**
|
||||
Optional values to be used for `Field` properties.
|
||||
*/
|
||||
public protocol FieldOptionalType: ExpressibleByNilLiteral {
|
||||
|
||||
/**
|
||||
The type for the wrapped value
|
||||
*/
|
||||
associatedtype Wrapped
|
||||
|
||||
/**
|
||||
Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
var cs_wrappedValue: Wrapped? { get }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Optional: FieldOptionalType
|
||||
|
||||
extension Optional: FieldOptionalType {
|
||||
|
||||
// MARK: FieldOptionalType
|
||||
|
||||
@inlinable
|
||||
public var cs_wrappedValue: Wrapped? {
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
38
Sources/FieldProtocol.swift
Normal file
38
Sources/FieldProtocol.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// 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 var dynamicObjectType: CoreStoreObject.Type { get }
|
||||
|
||||
static func read(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any?
|
||||
static func modify(field: FieldProtocol, for rawObject: CoreStoreManagedObject, newValue: Any?)
|
||||
}
|
||||
49
Sources/FieldRelationshipProtocol.swift
Normal file
49
Sources/FieldRelationshipProtocol.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// FieldRelationshipProtocol.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: - FieldRelationshipProtocol
|
||||
|
||||
internal protocol FieldRelationshipProtocol: FieldProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
isToMany: Bool,
|
||||
isOrdered: Bool,
|
||||
deleteRule: NSDeleteRule,
|
||||
inverse: (type: CoreStoreObject.Type, KeyPathString?),
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
affectedByKeyPaths: Set<KeyPathString>,
|
||||
minCount: Int,
|
||||
maxCount: Int
|
||||
)
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
|
||||
static func valueForSnapshot(field: FieldProtocol, for rawObject: CoreStoreManagedObject) -> Any?
|
||||
}
|
||||
262
Sources/FieldStorableType.swift
Normal file
262
Sources/FieldStorableType.swift
Normal file
@@ -0,0 +1,262 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/**
|
||||
Values to be used for `Field.Stored` properties.
|
||||
*/
|
||||
public protocol FieldStorableType {
|
||||
|
||||
/**
|
||||
The `NSAttributeType` for this type
|
||||
*/
|
||||
associatedtype FieldStoredNativeType
|
||||
|
||||
/**
|
||||
The `NSAttributeType` for this type. Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
static var cs_rawAttributeType: NSAttributeType { get }
|
||||
|
||||
/**
|
||||
Creates an instance of this type from raw native value. Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@inline(__always)
|
||||
static func cs_fromFieldStoredNativeType(_ value: FieldStoredNativeType) -> Self
|
||||
|
||||
/**
|
||||
Creates `FieldStoredNativeType` value from this instance. Used internally by CoreStore. Do not call directly.
|
||||
*/
|
||||
@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<FieldStorableType>
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Sources/Internals.AnyFieldCoder.swift
Normal file
222
Sources/Internals.AnyFieldCoder.swift
Normal file
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// Internals.AnyFieldCoder.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
extension Internals {
|
||||
|
||||
// MARK: - AnyFieldCoder
|
||||
|
||||
internal struct AnyFieldCoder {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let transformerName: NSValueTransformerName
|
||||
internal let transformer: Foundation.ValueTransformer
|
||||
internal let encodeToStoredData: (_ fieldValue: Any?) -> Data?
|
||||
internal let decodeFromStoredData: (_ data: Data?) -> Any?
|
||||
|
||||
internal init<Coder: FieldCoderType>(_ fieldCoder: Coder.Type) {
|
||||
|
||||
let transformer = CustomValueTransformer(fieldCoder: fieldCoder)
|
||||
self.transformerName = transformer.id
|
||||
self.transformer = transformer
|
||||
self.encodeToStoredData = {
|
||||
|
||||
switch $0 {
|
||||
|
||||
case let value as Coder.FieldStoredValue:
|
||||
return fieldCoder.encodeToStoredData(value)
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
return fieldCoder.encodeToStoredData(valueBox.value as! Coder.FieldStoredValue?)
|
||||
|
||||
default:
|
||||
return fieldCoder.encodeToStoredData(nil)
|
||||
}
|
||||
}
|
||||
self.decodeFromStoredData = { fieldCoder.decodeFromStoredData($0) }
|
||||
}
|
||||
|
||||
internal init<V>(tag: UUID, encode: @escaping (V) -> Data?, decode: @escaping (Data?) -> V) {
|
||||
|
||||
let transformer = CustomValueTransformer(tag: tag)
|
||||
self.transformerName = transformer.id
|
||||
self.transformer = transformer
|
||||
self.encodeToStoredData = { encode($0 as! V) }
|
||||
self.decodeFromStoredData = { decode($0) }
|
||||
}
|
||||
|
||||
internal func register() {
|
||||
|
||||
let transformerName = self.transformerName
|
||||
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
|
||||
|
||||
if transformerName == .secureUnarchiveFromDataTransformerName {
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
switch transformerName {
|
||||
|
||||
case .keyedUnarchiveFromDataTransformerName,
|
||||
.unarchiveFromDataTransformerName,
|
||||
.isNotNilTransformerName,
|
||||
.isNilTransformerName,
|
||||
.negateBooleanTransformerName:
|
||||
return
|
||||
|
||||
case let transformerName:
|
||||
Self.cachedCoders[transformerName] = self
|
||||
|
||||
Foundation.ValueTransformer.setValueTransformer(
|
||||
self.transformer,
|
||||
forName: transformerName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate static var cachedCoders: [NSValueTransformerName: AnyFieldCoder] = [:]
|
||||
|
||||
|
||||
// MARK: - TransformableDefaultValueCodingBox
|
||||
|
||||
@objc(_CoreStore_Internals_TransformableDefaultValueCodingBox)
|
||||
internal final class TransformableDefaultValueCodingBox: NSObject, NSSecureCoding {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@objc
|
||||
internal dynamic let transformerName: String
|
||||
|
||||
@objc
|
||||
internal dynamic let data: Data
|
||||
|
||||
@nonobjc
|
||||
internal let value: Any?
|
||||
|
||||
internal init?(defaultValue: Any?, fieldCoder: Internals.AnyFieldCoder?) {
|
||||
|
||||
guard
|
||||
let fieldCoder = fieldCoder,
|
||||
let defaultValue = defaultValue,
|
||||
!(defaultValue is NSNull),
|
||||
let data = fieldCoder.encodeToStoredData(defaultValue)
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self.transformerName = fieldCoder.transformerName.rawValue
|
||||
self.value = defaultValue
|
||||
self.data = data
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSSecureCoding
|
||||
|
||||
@objc
|
||||
dynamic class var supportsSecureCoding: Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSCoding
|
||||
|
||||
@objc
|
||||
dynamic required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
guard
|
||||
case let transformerName as String = aDecoder.decodeObject(forKey: #keyPath(TransformableDefaultValueCodingBox.transformerName)),
|
||||
let transformer = ValueTransformer(forName: .init(transformerName)),
|
||||
case let data as Data = aDecoder.decodeObject(forKey: #keyPath(TransformableDefaultValueCodingBox.data)),
|
||||
let value = transformer.reverseTransformedValue(data)
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
self.transformerName = transformerName
|
||||
self.data = data
|
||||
self.value = value
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func encode(with coder: NSCoder) {
|
||||
|
||||
coder.encode(self.data, forKey: #keyPath(TransformableDefaultValueCodingBox.data))
|
||||
coder.encode(self.transformerName, forKey: #keyPath(TransformableDefaultValueCodingBox.transformerName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CustomValueTransformer
|
||||
|
||||
fileprivate final class CustomValueTransformer: ValueTransformer {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let id: NSValueTransformerName
|
||||
|
||||
fileprivate init<Coder: FieldCoderType>(fieldCoder: Coder.Type) {
|
||||
|
||||
self.id = .init(rawValue: "CoreStore.FieldCoders.CustomValueTransformer<\(String(reflecting: fieldCoder))>.transformerName")
|
||||
}
|
||||
|
||||
fileprivate init(tag: UUID) {
|
||||
|
||||
self.id = .init(rawValue: "CoreStore.FieldCoders.CustomValueTransformer<\(tag.uuidString)>.transformerName")
|
||||
}
|
||||
|
||||
|
||||
// MARK: ValueTransformer
|
||||
|
||||
override class func transformedValueClass() -> AnyClass {
|
||||
|
||||
return NSData.self
|
||||
}
|
||||
|
||||
override class func allowsReverseTransformation() -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override func transformedValue(_ value: Any?) -> Any? {
|
||||
|
||||
return AnyFieldCoder.cachedCoders[self.id]?.encodeToStoredData(value) as Data?
|
||||
}
|
||||
|
||||
override func reverseTransformedValue(_ value: Any?) -> Any? {
|
||||
|
||||
return AnyFieldCoder.cachedCoders[self.id]?.decodeFromStoredData(value as! Data?)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +359,42 @@ public func ~= <O: NSManagedObject, D: NSManagedObject, S: Sequence>(_ sequence:
|
||||
}
|
||||
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: FieldContainer<Root>.Stored<QueryableAttributeType & Equatable>
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$nickname == "John"))
|
||||
```
|
||||
*/
|
||||
public func == <O, V>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return Where<O>(keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is not equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$nickname != "John"))
|
||||
```
|
||||
*/
|
||||
public func != <O, V>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return !Where<O>(keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by checking if a sequence contains the value of a property
|
||||
```
|
||||
let dog = dataStack.fetchOne(From<Dog>().where(["Pluto", "Snoopy", "Scooby"] ~= \.nickname))
|
||||
```
|
||||
*/
|
||||
public func ~= <O, V, S: Sequence>(_ sequence: S, _ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>) -> Where<O> where S.Iterator.Element == V {
|
||||
|
||||
return Where<O>(O.meta[keyPath: keyPath].keyPath, isMemberOf: sequence)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer<Root>.Required<QueryableAttributeType & Equatable>
|
||||
|
||||
/**
|
||||
@@ -433,6 +469,17 @@ public func ~= <O, V, S: Sequence>(_ sequence: S, _ keyPath: KeyPath<O, ValueCon
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer<Root>.Required<QueryableAttributeType & Comparable>
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age < 20))
|
||||
```
|
||||
*/
|
||||
public func < <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return Where<O>("%K < %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than a value
|
||||
```
|
||||
@@ -444,6 +491,17 @@ public func < <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Require
|
||||
return Where<O>("%K < %@", O.meta[keyPath: keyPath].keyPath, value.cs_toQueryableNativeType())
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age > 20))
|
||||
```
|
||||
*/
|
||||
public func > <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return Where<O>("%K > %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than a value
|
||||
```
|
||||
@@ -455,6 +513,17 @@ public func > <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Require
|
||||
return Where<O>("%K > %@", O.meta[keyPath: keyPath].keyPath, value.cs_toQueryableNativeType())
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than or equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age <= 20))
|
||||
```
|
||||
*/
|
||||
public func <= <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return Where<O>("%K <= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than or equal to a value
|
||||
```
|
||||
@@ -466,6 +535,17 @@ public func <= <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Requir
|
||||
return Where<O>("%K <= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toQueryableNativeType())
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than or equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age >= 20))
|
||||
```
|
||||
*/
|
||||
public func >= <O, V: Comparable>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> {
|
||||
|
||||
return Where<O>("%K >= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than or equal to a value
|
||||
```
|
||||
@@ -480,6 +560,17 @@ public func >= <O, V: Comparable>(_ keyPath: KeyPath<O, ValueContainer<O>.Requir
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: ValueContainer<Root>.Optional<QueryableAttributeType & Comparable>
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age < 20))
|
||||
```
|
||||
*/
|
||||
public func < <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
|
||||
|
||||
return Where<O>("%K < %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than a value
|
||||
```
|
||||
@@ -498,6 +589,17 @@ public func < <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ val
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age > 20))
|
||||
```
|
||||
*/
|
||||
public func > <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
|
||||
|
||||
return Where<O>("%K > %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than a value
|
||||
```
|
||||
@@ -516,6 +618,17 @@ public func > <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ val
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than or equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age <= 20))
|
||||
```
|
||||
*/
|
||||
public func <= <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
|
||||
|
||||
return Where<O>("%K <= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is less than or equal to a value
|
||||
```
|
||||
@@ -534,6 +647,17 @@ public func <= <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ va
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than or equal to a value
|
||||
```
|
||||
let person = dataStack.fetchOne(From<Person>().where(\.$age >= 20))
|
||||
```
|
||||
*/
|
||||
public func >= <O, V: FieldOptionalType>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, _ value: V) -> Where<O> where V.Wrapped: Comparable {
|
||||
|
||||
return Where<O>("%K >= %@", O.meta[keyPath: keyPath].keyPath, value.cs_toFieldStoredNativeType() as! V.FieldStoredNativeType)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is greater than or equal to a value
|
||||
```
|
||||
@@ -555,6 +679,17 @@ public func >= <O, V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, _ va
|
||||
|
||||
// MARK: - KeyPath where Root: CoreStoreObject, Value: RelationshipContainer<Root>.ToOne<CoreStoreObject>
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is equal to a value
|
||||
```
|
||||
let dog = dataStack.fetchOne(From<Dog>().where(\.$master == john))
|
||||
```
|
||||
*/
|
||||
public func == <O, D: FieldRelationshipToOneType>(_ keyPath: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ object: D.DestinationObjectType?) -> Where<O> {
|
||||
|
||||
return Where<O>(O.meta[keyPath: keyPath].keyPath, isEqualTo: object)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is equal to a value
|
||||
```
|
||||
@@ -577,6 +712,17 @@ public func == <O, D>(_ keyPath: KeyPath<O, RelationshipContainer<O>.ToOne<D>>,
|
||||
return Where<O>(O.meta[keyPath: keyPath].keyPath, isEqualTo: object)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is not equal to a value
|
||||
```
|
||||
let dog = dataStack.fetchOne(From<Dog>().where(\.$master != john))
|
||||
```
|
||||
*/
|
||||
public func != <O, D: FieldRelationshipToOneType>(_ keyPath: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ object: D.DestinationObjectType?) -> Where<O> {
|
||||
|
||||
return !Where<O>(O.meta[keyPath: keyPath].keyPath, isEqualTo: object)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by comparing if a property is not equal to a value
|
||||
```
|
||||
@@ -599,6 +745,17 @@ public func != <O, D>(_ keyPath: KeyPath<O, RelationshipContainer<O>.ToOne<D>>,
|
||||
return !Where<O>(O.meta[keyPath: keyPath].keyPath, isEqualTo: object)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by checking if a sequence contains a value of a property
|
||||
```
|
||||
let dog = dataStack.fetchOne(From<Dog>().where([john, bob, joe] ~= \.$master))
|
||||
```
|
||||
*/
|
||||
public func ~= <O, D: FieldRelationshipToOneType, S: Sequence>(_ sequence: S, _ keyPath: KeyPath<O, FieldContainer<O>.Relationship<D>>) -> Where<O> where S.Iterator.Element == D.DestinationObjectType {
|
||||
|
||||
return Where<O>(O.meta[keyPath: keyPath].keyPath, isMemberOf: sequence)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `Where` clause by checking if a sequence contains a value of a property
|
||||
```
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -33,29 +33,49 @@ extension NSEntityDescription {
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveAttributeNames() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] {
|
||||
return self.attributesByName.reduce(into: [:], { (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
result[attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
})
|
||||
|
||||
return self.attributesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
|
||||
result[attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveAttributeRenamingIdentities() -> [String: (attribute: NSAttributeDescription, versionHash: Data)] {
|
||||
return self.attributesByName.reduce(into: [:], { (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
result[attribute.description.renamingIdentifier ?? attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
})
|
||||
|
||||
return self.attributesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, attribute: (name: String, description: NSAttributeDescription)) in
|
||||
|
||||
result[attribute.description.renamingIdentifier ?? attribute.name] = (attribute.description, attribute.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveRelationshipNames() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] {
|
||||
return self.relationshipsByName.reduce(into: [:], { (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
result[relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
})
|
||||
|
||||
return self.relationshipsByName.reduce(
|
||||
into: [:],
|
||||
{ (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
|
||||
result[relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveRelationshipRenamingIdentities() -> [String: (relationship: NSRelationshipDescription, versionHash: Data)] {
|
||||
return self.relationshipsByName.reduce(into: [:], { (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
result[relationship.description.renamingIdentifier ?? relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
})
|
||||
|
||||
return self.relationshipsByName.reduce(
|
||||
into: [:],
|
||||
{ (result, relationship: (name: String, description: NSRelationshipDescription)) in
|
||||
|
||||
result[relationship.description.renamingIdentifier ?? relationship.name] = (relationship.description, relationship.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,15 +33,25 @@ extension NSManagedObjectModel {
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveNames() -> [String: (entity: NSEntityDescription, versionHash: Data)] {
|
||||
return self.entitiesByName.reduce(into: [:], { (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
result[entity.name] = (entity.description, entity.description.versionHash)
|
||||
})
|
||||
|
||||
return self.entitiesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
|
||||
result[entity.name] = (entity.description, entity.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func cs_resolveRenamingIdentities() -> [String: (entity: NSEntityDescription, versionHash: Data)] {
|
||||
return self.entitiesByName.reduce(into: [:], { (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
result[entity.description.renamingIdentifier ?? entity.name] = (entity.description, entity.description.versionHash)
|
||||
})
|
||||
|
||||
return self.entitiesByName.reduce(
|
||||
into: [:],
|
||||
{ (result, entity: (name: String, description: NSEntityDescription)) in
|
||||
|
||||
result[entity.description.renamingIdentifier ?? entity.name] = (entity.description, entity.description.versionHash)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
235
Sources/ObjectProxy.swift
Normal file
235
Sources/ObjectProxy.swift
Normal file
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// ObjectProxy.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: - ObjectProxy
|
||||
|
||||
/**
|
||||
An `ObjectProxy` is only used when overriding getters and setters for `CoreStoreObject` properties. Custom getters and setters are implemented as a closure that "overrides" the default property getter/setter. The closure receives an `ObjectProxy<O>`, 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 every time KVO invokes this accessor method incurs a heavy performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `ObjectProxy<O>`, make sure to use `ObjectProxy<O>.$property.primitiveValue` instead of `ObjectProxy<O>.$property.value`, which would execute the same accessor again recursively.
|
||||
*/
|
||||
@dynamicMemberLookup
|
||||
public struct ObjectProxy<O: CoreStoreObject> {
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> FieldProxy<V> {
|
||||
|
||||
return .init(rawObject: self.rawObject, keyPath: member)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) -> FieldProxy<V> {
|
||||
|
||||
return .init(rawObject: self.rawObject, keyPath: member)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Coded<V>>) -> FieldProxy<V> {
|
||||
|
||||
return .init(rawObject: self.rawObject, keyPath: member)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let rawObject: CoreStoreManagedObject
|
||||
|
||||
internal init(_ rawObject: CoreStoreManagedObject) {
|
||||
|
||||
self.rawObject = rawObject
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FieldProxy
|
||||
|
||||
public struct FieldProxy<V> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the value for the specified property from the object’s private internal storage.
|
||||
|
||||
Accessing this property does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily to implement custom accessor methods that need direct access to the object's private storage.
|
||||
*/
|
||||
public var primitiveValue: V? {
|
||||
|
||||
get {
|
||||
|
||||
return self.getPrimitiveValue()
|
||||
}
|
||||
nonmutating set {
|
||||
|
||||
self.setPrimitiveValue(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
|
||||
Accessing this property triggers the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`).
|
||||
*/
|
||||
public var value: V {
|
||||
|
||||
get {
|
||||
|
||||
return self.getValue()
|
||||
}
|
||||
nonmutating set {
|
||||
|
||||
self.setValue(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init<OBase>(rawObject: CoreStoreManagedObject, keyPath: KeyPath<O, FieldContainer<OBase>.Stored<V>>) {
|
||||
|
||||
self.init(rawObject: rawObject, field: O.meta[keyPath: keyPath])
|
||||
}
|
||||
|
||||
internal init<OBase>(rawObject: CoreStoreManagedObject, keyPath: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) {
|
||||
|
||||
self.init(rawObject: rawObject, field: O.meta[keyPath: keyPath])
|
||||
}
|
||||
|
||||
internal init<OBase>(rawObject: CoreStoreManagedObject, keyPath: KeyPath<O, FieldContainer<OBase>.Coded<V>>) {
|
||||
|
||||
self.init(rawObject: rawObject, field: O.meta[keyPath: keyPath])
|
||||
}
|
||||
|
||||
internal init<OBase>(rawObject: CoreStoreManagedObject, field: FieldContainer<OBase>.Stored<V>) {
|
||||
|
||||
let keyPathString = field.keyPath
|
||||
self.getValue = {
|
||||
|
||||
return type(of: field).read(field: field, for: rawObject) as! V
|
||||
}
|
||||
self.setValue = {
|
||||
|
||||
type(of: field).modify(field: field, for: rawObject, newValue: $0)
|
||||
}
|
||||
self.getPrimitiveValue = {
|
||||
|
||||
return V.cs_fromFieldStoredNativeType(
|
||||
rawObject.primitiveValue(forKey: keyPathString) as! V.FieldStoredNativeType
|
||||
)
|
||||
}
|
||||
self.setPrimitiveValue = {
|
||||
|
||||
rawObject.setPrimitiveValue(
|
||||
$0.cs_toFieldStoredNativeType(),
|
||||
forKey: keyPathString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal init<OBase>(rawObject: CoreStoreManagedObject, field: FieldContainer<OBase>.Virtual<V>) {
|
||||
|
||||
let keyPathString = field.keyPath
|
||||
self.getValue = {
|
||||
|
||||
return type(of: field).read(field: field, for: rawObject) as! V
|
||||
}
|
||||
self.setValue = {
|
||||
|
||||
type(of: field).modify(field: field, for: rawObject, newValue: $0)
|
||||
}
|
||||
self.getPrimitiveValue = {
|
||||
|
||||
switch rawObject.primitiveValue(forKey: keyPathString) {
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.setPrimitiveValue = {
|
||||
|
||||
rawObject.setPrimitiveValue(
|
||||
$0,
|
||||
forKey: keyPathString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal init<OBase>(rawObject: CoreStoreManagedObject, field: FieldContainer<OBase>.Coded<V>) {
|
||||
|
||||
let keyPathString = field.keyPath
|
||||
self.getValue = {
|
||||
|
||||
return type(of: field).read(field: field, for: rawObject) as! V
|
||||
}
|
||||
self.setValue = {
|
||||
|
||||
type(of: field).modify(field: field, for: rawObject, newValue: $0)
|
||||
}
|
||||
self.getPrimitiveValue = {
|
||||
|
||||
switch rawObject.primitiveValue(forKey: keyPathString) {
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
rawObject.setPrimitiveValue(valueBox.value, forKey: keyPathString)
|
||||
return valueBox.value as? V
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.setPrimitiveValue = {
|
||||
|
||||
rawObject.setPrimitiveValue(
|
||||
$0,
|
||||
forKey: keyPathString
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let getValue: () -> V
|
||||
private let setValue: (V) -> Void
|
||||
private let getPrimitiveValue: () -> V?
|
||||
private let setPrimitiveValue: (V?) -> Void
|
||||
}
|
||||
}
|
||||
@@ -367,6 +367,87 @@ extension ObjectPublisher where O: CoreStoreObject {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> V? {
|
||||
|
||||
guard
|
||||
let object = self.object,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = object[keyPath: member]
|
||||
return type(of: field).read(field: field, for: rawObject) as! V?
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) -> V? {
|
||||
|
||||
guard
|
||||
let object = self.object,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = object[keyPath: member]
|
||||
return type(of: field).read(field: field, for: rawObject) as! V?
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Coded<V>>) -> V? {
|
||||
|
||||
guard
|
||||
let object = self.object,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = object[keyPath: member]
|
||||
return type(of: field).read(field: field, for: rawObject) as! V?
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Relationship<V>>) -> V.PublishedType? {
|
||||
|
||||
guard
|
||||
let object = self.object,
|
||||
let rawObject = object.rawObject
|
||||
else {
|
||||
|
||||
return nil
|
||||
}
|
||||
Internals.assert(
|
||||
rawObject.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
let field = object[keyPath: member]
|
||||
let snapshotValue = V.cs_valueForSnapshot(from: rawObject.objectIDs(forRelationshipNamed: field.keyPath))
|
||||
return V.cs_toPublishedType(from: snapshotValue, in: self.context)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
|
||||
@@ -175,6 +175,76 @@ extension ObjectSnapshot where O: NSManagedObject {
|
||||
|
||||
extension ObjectSnapshot where O: CoreStoreObject {
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
public subscript<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Stored<V>>) -> 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<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Virtual<V>>) -> 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<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Coded<V>>) -> 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<OBase, V>(dynamicMember member: KeyPath<O, FieldContainer<OBase>.Relationship<V>>) -> V.PublishedType {
|
||||
|
||||
get {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
let context = self.context
|
||||
let snapshotValue = self.values[key] as! V.SnapshotValueType
|
||||
return V.cs_toPublishedType(from: snapshotValue, in: context)
|
||||
}
|
||||
set {
|
||||
|
||||
let key = String(keyPath: member)
|
||||
self.values[key] = V.cs_toSnapshotType(from: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the property identified by a given key.
|
||||
*/
|
||||
|
||||
@@ -185,6 +185,14 @@ public struct OrderBy<O: DynamicObject>: OrderByClause, FetchClause, QueryClause
|
||||
|
||||
|
||||
// MARK: CoreStoreObject Key Paths
|
||||
|
||||
/**
|
||||
Indicates that the `KeyPathString` should be sorted in ascending order
|
||||
*/
|
||||
public static func ascending<T>(_ attribute: KeyPath<O, FieldContainer<O>.Stored<T>>) -> SortKey {
|
||||
|
||||
return .ascending(O.meta[keyPath: attribute].keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates that the `KeyPathString` should be sorted in ascending order
|
||||
@@ -217,6 +225,14 @@ public struct OrderBy<O: DynamicObject>: OrderByClause, FetchClause, QueryClause
|
||||
|
||||
return .ascending(O.meta[keyPath: attribute].keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates that the `KeyPathString` should be sorted in descending order
|
||||
*/
|
||||
public static func descending<T>(_ attribute: KeyPath<O, FieldContainer<O>.Stored<T>>) -> SortKey {
|
||||
|
||||
return .descending(O.meta[keyPath: attribute].keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Indicates that the `KeyPathString` should be sorted in descending order
|
||||
|
||||
@@ -41,6 +41,117 @@ public struct PartialObject<O: CoreStoreObject> {
|
||||
|
||||
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<V>(for property: (O) -> FieldContainer<O>.Stored<V>) -> 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<V>(for property: (O) -> FieldContainer<O>.Virtual<V>) -> V {
|
||||
|
||||
switch self.rawObject.value(forKey: property(O.meta).keyPath) {
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil as Any? as! V // There should be no uninitialized state at this point
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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<V>(for property: (O) -> FieldContainer<O>.Stored<V>) -> 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<V>(for property: (O) -> FieldContainer<O>.Virtual<V>) -> V? {
|
||||
|
||||
switch self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) {
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the value for the specified property from the managed object’s private internal storage.
|
||||
|
||||
This method does not invoke the access notification methods (`willAccessValue(forKey:)` and `didAccessValue(forKey:)`). This method is used primarily by subclasses that implement custom accessor methods that need direct access to the receiver’s private storage.
|
||||
*/
|
||||
public func primitiveValue<V>(for property: (O) -> FieldContainer<O>.Coded<V>) -> V? {
|
||||
|
||||
let keyPath = property(O.meta).keyPath
|
||||
switch self.rawObject.primitiveValue(forKey: keyPath) {
|
||||
|
||||
case let valueBox as Internals.AnyFieldCoder.TransformableDefaultValueCodingBox:
|
||||
rawObject.setPrimitiveValue(valueBox.value, forKey: keyPath)
|
||||
return valueBox.value as? V
|
||||
|
||||
case let value as V:
|
||||
return value
|
||||
|
||||
case nil,
|
||||
is NSNull,
|
||||
_? /* any other unrelated type */ :
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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<V>(_ value: V, for property: (O) -> FieldContainer<O>.Stored<V>) {
|
||||
|
||||
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<V>(_ value: V, for property: (O) -> FieldContainer<O>.Virtual<V>) {
|
||||
|
||||
self.rawObject.setPrimitiveValue(
|
||||
value,
|
||||
forKey: property(O.meta).keyPath
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Value.Required accessors/mutators
|
||||
|
||||
@@ -29,7 +29,7 @@ import CoreData
|
||||
|
||||
// MARK: - PropertyProtocol
|
||||
|
||||
internal protocol PropertyProtocol: AnyObject {
|
||||
internal protocol PropertyProtocol {
|
||||
|
||||
var keyPath: KeyPathString { get }
|
||||
}
|
||||
|
||||
459
Sources/Relationship.ToManyOrdered.swift
Normal file
459
Sources/Relationship.ToManyOrdered.swift
Normal file
@@ -0,0 +1,459 @@
|
||||
//
|
||||
// Relationship.ToManyOrdered.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - RelationshipContainer
|
||||
|
||||
extension RelationshipContainer {
|
||||
|
||||
// MARK: - ToManyOrdered
|
||||
|
||||
/**
|
||||
The containing type for to-many ordered relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- Important: `Relationship.ToManyOrdered` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class ToManyOrdered<D: CoreStoreObject>: ToManyRelationshipKeyPathStringConvertible, RelationshipProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
inverseKeyPath: { nil },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
The relationship value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
return self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) })
|
||||
}
|
||||
set {
|
||||
|
||||
self.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = D
|
||||
|
||||
|
||||
// MARK: RelationshipKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = [DestinationValueType]
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: RelationshipProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal var nativeValue: NSOrderedSet {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
return object.getValue(
|
||||
forKvcKey: self.keyPath,
|
||||
didGetValue: { ($0 as! NSOrderedSet?) ?? [] }
|
||||
)
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKvcKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value.map({ $0.objectID() })
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
let range = (Swift.max(0, minCount) ... maxCount)
|
||||
return (
|
||||
isToMany: true,
|
||||
isOrdered: true,
|
||||
deleteRule: deleteRule.nativeValue,
|
||||
inverse: (type: D.self, keyPath: inverseKeyPath()),
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
minCount: range.lowerBound,
|
||||
maxCount: range.upperBound
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
extension RelationshipContainer.ToManyOrdered: RandomAccessCollection {
|
||||
|
||||
// MARK: Sequence
|
||||
|
||||
public typealias Iterator = AnyIterator<D>
|
||||
|
||||
public func makeIterator() -> Iterator {
|
||||
|
||||
var iterator = self.nativeValue.makeIterator()
|
||||
return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) })
|
||||
}
|
||||
|
||||
|
||||
// MARK: Collection
|
||||
|
||||
public typealias Index = Int
|
||||
|
||||
public var startIndex: Index {
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
|
||||
return self.nativeValue.count
|
||||
}
|
||||
|
||||
public subscript(position: Index) -> Iterator.Element {
|
||||
|
||||
return D.cs_fromRaw(object: self.nativeValue[position] as! NSManagedObject)
|
||||
}
|
||||
|
||||
public func index(after i: Index) -> Index {
|
||||
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension RelationshipContainer.ToManyOrdered {
|
||||
|
||||
/**
|
||||
Assigns a sequence of objects to the relationship. The operation
|
||||
```
|
||||
person.pets .= [dog, cat]
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
person.pets.value = [dog, cat]
|
||||
```
|
||||
*/
|
||||
public static func .= <S: Sequence>(_ relationship: RelationshipContainer<O>.ToManyOrdered<D>, _ newValue: S) where S.Iterator.Element == D {
|
||||
|
||||
relationship.nativeValue = NSOrderedSet(array: newValue.map({ $0.rawObject! }))
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a sequence of objects to the relationship. The operation
|
||||
```
|
||||
person.pets .= anotherPerson.pets
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
person.pets.value = anotherPerson.pets.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ relationship: RelationshipContainer<O>.ToManyOrdered<D>, _ relationship2: RelationshipContainer<O2>.ToManyOrdered<D>) {
|
||||
|
||||
relationship.nativeValue = relationship2.nativeValue
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a relationship's objects and a collection of objects
|
||||
```
|
||||
if person.pets .== [dog, cat] { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if person.pets.value == [dog, cat] { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== <C: Collection>(_ relationship: RelationshipContainer<O>.ToManyOrdered<D>, _ collection: C) -> Bool where C.Iterator.Element == D {
|
||||
|
||||
return relationship.nativeValue.elementsEqual(
|
||||
collection.lazy.map({ $0.rawObject! }),
|
||||
by: { ($0 as! NSManagedObject) == $1 }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a collection of objects and a relationship's objects
|
||||
```
|
||||
if [dog, cat] .== person.pets { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if [dog, cat] == person.pets.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== <C: Collection>(_ collection: C, _ relationship: RelationshipContainer<O>.ToManyOrdered<D>) -> Bool where C.Iterator.Element == D {
|
||||
|
||||
return relationship.nativeValue.elementsEqual(
|
||||
collection.lazy.map({ $0.rawObject! }),
|
||||
by: { ($0 as! NSManagedObject) == $1 }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a relationship's objects and a collection of objects
|
||||
```
|
||||
if person.pets .== anotherPerson.pets { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if person.pets.value == anotherPerson.pets.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== <O2>(_ relationship: RelationshipContainer<O>.ToManyOrdered<D>, _ relationship2: RelationshipContainer<O2>.ToManyOrdered<D>) -> Bool {
|
||||
|
||||
return relationship.nativeValue == relationship2.nativeValue
|
||||
}
|
||||
}
|
||||
461
Sources/Relationship.ToManyUnordered.swift
Normal file
461
Sources/Relationship.ToManyUnordered.swift
Normal file
@@ -0,0 +1,461 @@
|
||||
//
|
||||
// Relationship.ToManyUnordered.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - RelationshipContainer
|
||||
|
||||
extension RelationshipContainer {
|
||||
|
||||
// MARK: - ToManyUnordered
|
||||
|
||||
/**
|
||||
The containing type for to-many unordered relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyUnordered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- Important: `Relationship.ToManyUnordered` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class ToManyUnordered<D: CoreStoreObject>: ToManyRelationshipKeyPathStringConvertible, RelationshipProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { nil },
|
||||
deleteRule: deleteRule,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyOrdered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter minCount: the minimum number of objects in this relationship UNLESS THE RELATIONSHIP IS EMPTY. This means there might be zero objects in the relationship, which might be less than `minCount`. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter maxCount: the maximum number of objects in this relationship. If the number of objects in the relationship do not satisfy `minCount` and `maxCount`, the transaction's commit (or auto-commit) would fail with a validation error.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
minCount: Int = 0,
|
||||
maxCount: Int = 0,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
minCount: minCount,
|
||||
maxCount: maxCount,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
The relationship value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
return Set(self.nativeValue.map({ D.cs_fromRaw(object: $0 as! NSManagedObject) }))
|
||||
}
|
||||
set {
|
||||
|
||||
self.nativeValue = NSSet(array: newValue.map({ $0.rawObject! }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = D
|
||||
|
||||
|
||||
// MARK: RelationshipKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = Set<DestinationValueType>
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: RelationshipProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal var nativeValue: NSSet {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
return object.getValue(
|
||||
forKvcKey: self.keyPath,
|
||||
didGetValue: { ($0 as! NSSet?) ?? [] }
|
||||
)
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKvcKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return Set(self.value.map({ $0.objectID() }))
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
let range = (Swift.max(0, minCount) ... maxCount)
|
||||
return (
|
||||
isToMany: true,
|
||||
isOrdered: false,
|
||||
deleteRule: deleteRule.nativeValue,
|
||||
inverse: (type: D.self, keyPath: inverseKeyPath()),
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
minCount: range.lowerBound,
|
||||
maxCount: range.upperBound
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
extension RelationshipContainer.ToManyUnordered: Sequence {
|
||||
|
||||
/**
|
||||
The number of elements in the set.
|
||||
*/
|
||||
public var count: Int {
|
||||
|
||||
return self.nativeValue.count
|
||||
}
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the range contains no elements.
|
||||
*/
|
||||
public var isEmpty: Bool {
|
||||
|
||||
return self.nativeValue.count == 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: Sequence
|
||||
|
||||
public typealias Iterator = AnyIterator<D>
|
||||
|
||||
public func makeIterator() -> Iterator {
|
||||
|
||||
var iterator = self.nativeValue.makeIterator()
|
||||
return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension RelationshipContainer.ToManyUnordered {
|
||||
|
||||
/**
|
||||
Assigns a sequence of objects to the relationship. The operation
|
||||
```
|
||||
person.pets .= [dog, cat]
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
person.pets.value = [dog, cat]
|
||||
```
|
||||
*/
|
||||
public static func .= <S: Sequence>(_ relationship: RelationshipContainer<O>.ToManyUnordered<D>, _ newValue: S) where S.Iterator.Element == D {
|
||||
|
||||
relationship.nativeValue = NSSet(array: newValue.map({ $0.rawObject! }))
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a sequence of objects to the relationship. The operation
|
||||
```
|
||||
person.pets .= anotherPerson.pets
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
person.pets.value = anotherPerson.pets.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ relationship: RelationshipContainer<O>.ToManyUnordered<D>, _ relationship2: RelationshipContainer<O2>.ToManyUnordered<D>) {
|
||||
|
||||
relationship.nativeValue = relationship2.nativeValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a sequence of objects to the relationship. The operation
|
||||
```
|
||||
person.pets .= anotherPerson.pets
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
person.pets.value = anotherPerson.pets.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ relationship: RelationshipContainer<O>.ToManyUnordered<D>, _ relationship2: RelationshipContainer<O2>.ToManyOrdered<D>) {
|
||||
|
||||
relationship.nativeValue = NSSet(set: relationship2.nativeValue.set)
|
||||
}
|
||||
|
||||
/**
|
||||
Compares the if the relationship's objects and a set of objects have the same elements.
|
||||
```
|
||||
if person.pets .== Set<Animal>([dog, cat]) { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if person.pets.value == Set<Animal>([dog, cat]) { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ relationship: RelationshipContainer<O>.ToManyUnordered<D>, _ set: Set<D>) -> Bool {
|
||||
|
||||
return relationship.nativeValue.isEqual(to: Set(set.map({ $0.rawObject! })))
|
||||
}
|
||||
|
||||
/**
|
||||
Compares if a set of objects and a relationship's objects have the same elements.
|
||||
```
|
||||
if Set<Animal>([dog, cat]) .== person.pets { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if Set<Animal>([dog, cat]) == person.pets.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ set: Set<D>, _ relationship: RelationshipContainer<O>.ToManyUnordered<D>) -> Bool {
|
||||
|
||||
return relationship.nativeValue.isEqual(to: Set(set.map({ $0.rawObject! })))
|
||||
}
|
||||
|
||||
/**
|
||||
Compares if a relationship's objects and another relationship's objects have the same elements.
|
||||
```
|
||||
if person.pets .== anotherPerson.pets { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if person.pets.value == anotherPerson.pets.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== <O2>(_ relationship: RelationshipContainer<O>.ToManyUnordered<D>, _ relationship2: RelationshipContainer<O2>.ToManyUnordered<D>) -> Bool {
|
||||
|
||||
return relationship.nativeValue.isEqual(relationship2.nativeValue)
|
||||
}
|
||||
}
|
||||
387
Sources/Relationship.ToOne.swift
Normal file
387
Sources/Relationship.ToOne.swift
Normal file
@@ -0,0 +1,387 @@
|
||||
//
|
||||
// Relationship.ToOne.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - RelationshipContainer
|
||||
|
||||
extension RelationshipContainer {
|
||||
|
||||
// MARK: - ToOne
|
||||
|
||||
/**
|
||||
The containing type for to-one relationships. Any `CoreStoreObject` subclass can be a destination type. Inverse relationships should be declared from the destination type as well, using the `inverse:` argument for the relationship.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyUnordered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- Important: `Relationship.ToOne` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class ToOne<D: CoreStoreObject>: RelationshipKeyPathStringConvertible, RelationshipProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyUnordered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { nil },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyUnordered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyUnordered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes the metadata for the relationship. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object. Make sure to declare this relationship's inverse relationship on its destination object. Due to Swift's compiler limitation, only one of the relationship and its inverse can declare an `inverse:` argument.
|
||||
```
|
||||
class Dog: CoreStoreObject {
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
let pets = Relationship.ToManyUnordered<Dog>("pets", inverse: { $0.master })
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent name for this relationship.
|
||||
- parameter inverse: the inverse relationship that is declared for the destination object. All relationships require an "inverse", so updates to to this object's relationship are also reflected on its destination object.
|
||||
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
|
||||
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public convenience init(
|
||||
_ keyPath: KeyPathString,
|
||||
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
|
||||
deleteRule: DeleteRule = .nullify,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.init(
|
||||
keyPath: keyPath,
|
||||
inverseKeyPath: { inverse(D.meta).keyPath },
|
||||
deleteRule: deleteRule,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
The relationship value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
return self.nativeValue.flatMap(D.cs_fromRaw)
|
||||
}
|
||||
set {
|
||||
|
||||
self.nativeValue = newValue?.rawObject
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = D
|
||||
|
||||
|
||||
// MARK: RelationshipKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType?
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: RelationshipProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> RelationshipProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal var nativeValue: NSManagedObject? {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
return object.getValue(
|
||||
forKvcKey: self.keyPath,
|
||||
didGetValue: { $0 as! NSManagedObject? }
|
||||
)
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKvcKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value?.objectID()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private init(keyPath: KeyPathString, inverseKeyPath: @escaping () -> KeyPathString?, deleteRule: RelationshipContainer.DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
isToMany: false,
|
||||
isOrdered: false,
|
||||
deleteRule: deleteRule.nativeValue,
|
||||
inverse: (type: D.self, keyPath: inverseKeyPath()),
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
minCount: 0,
|
||||
maxCount: 1
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension RelationshipContainer.ToOne {
|
||||
|
||||
/**
|
||||
Assigns an object to the relationship. The operation
|
||||
```
|
||||
dog.master .= person
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
dog.master.value = person
|
||||
```
|
||||
*/
|
||||
public static func .= (_ relationship: RelationshipContainer<O>.ToOne<D>, _ newObject: D?) {
|
||||
|
||||
relationship.nativeValue = newObject?.cs_toRaw()
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns an object from another relationship. The operation
|
||||
```
|
||||
dog.master .= anotherDog.master
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
dog.master.value = anotherDog.master.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ relationship: RelationshipContainer<O>.ToOne<D>, _ relationship2: RelationshipContainer<O2>.ToOne<D>) {
|
||||
|
||||
relationship.nativeValue = relationship2.nativeValue
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a relationship's object and another object
|
||||
```
|
||||
if dog.master .== person { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if dog.master.value == person { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Bool {
|
||||
|
||||
return relationship.nativeValue == object?.cs_toRaw()
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between an object and a relationship's object
|
||||
```
|
||||
if dog.master .== person { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if dog.master.value == person { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ object: D?, _ relationship: RelationshipContainer<O>.ToOne<D>) -> Bool {
|
||||
|
||||
return object?.cs_toRaw() == relationship.nativeValue
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a relationship's object and another relationship's object
|
||||
```
|
||||
if dog.master .== person { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if dog.master.value == person { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== <O2>(_ relationship: RelationshipContainer<O>.ToOne<D>, _ relationship2: RelationshipContainer<O2>.ToOne<D>) -> Bool {
|
||||
|
||||
return relationship.nativeValue == relationship2.nativeValue
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,17 +29,22 @@ import CoreData
|
||||
|
||||
// MARK: - RelationshipProtocol
|
||||
|
||||
internal protocol RelationshipProtocol: PropertyProtocol {
|
||||
internal protocol RelationshipProtocol: AnyObject, PropertyProtocol {
|
||||
|
||||
typealias EntityDescriptionValues = (
|
||||
isToMany: Bool,
|
||||
isOrdered: Bool,
|
||||
deleteRule: NSDeleteRule,
|
||||
inverse: (type: CoreStoreObject.Type, KeyPathString?),
|
||||
versionHashModifier: String?,
|
||||
renamingIdentifier: String?,
|
||||
affectedByKeyPaths: Set<String>,
|
||||
minCount: Int,
|
||||
maxCount: Int
|
||||
)
|
||||
|
||||
var entityDescriptionValues: () -> EntityDescriptionValues { get }
|
||||
|
||||
var isToMany: Bool { get }
|
||||
var isOrdered: Bool { get }
|
||||
var deleteRule: NSDeleteRule { get }
|
||||
var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPathString?) { get }
|
||||
var affectedByKeyPaths: () -> Set<String> { get }
|
||||
var rawObject: CoreStoreManagedObject? { get set }
|
||||
var versionHashModifier: () -> String? { get }
|
||||
var renamingIdentifier: () -> String? { get }
|
||||
var minCount: Int { get }
|
||||
var maxCount: Int { get }
|
||||
var valueForSnapshot: Any? { get }
|
||||
}
|
||||
|
||||
@@ -379,45 +379,15 @@ extension SelectTerm where O: NSManagedObject {
|
||||
// MARK: - SelectTerm where O: CoreStoreObject
|
||||
|
||||
extension SelectTerm where O: CoreStoreObject {
|
||||
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
public static func attribute<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>) -> SelectTerm<O> {
|
||||
|
||||
return self.attribute(O.meta[keyPath: keyPath].keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
public static func attribute<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>) -> SelectTerm<O> {
|
||||
|
||||
return self.attribute(O.meta[keyPath: keyPath].keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
public static func attribute<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>) -> SelectTerm<O> {
|
||||
|
||||
return self.attribute(O.meta[keyPath: keyPath].keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
public static func attribute<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>) -> SelectTerm<O> {
|
||||
|
||||
return self.attribute(O.meta[keyPath: keyPath].keyPath)
|
||||
public static func attribute<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>) -> SelectTerm<O> where K.ObjectType == O {
|
||||
|
||||
return self.attribute(O.meta[keyPath: keyPath].cs_keyPathString)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,42 +396,9 @@ extension SelectTerm where O: CoreStoreObject {
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
*/
|
||||
public static func average<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
public static func average<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>, as alias: KeyPathString? = nil) -> SelectTerm<O> where K.ObjectType == O{
|
||||
|
||||
return self.average(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
*/
|
||||
public static func average<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.average(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
*/
|
||||
public static func average<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.average(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
*/
|
||||
public static func average<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.average(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
return self.average(O.meta[keyPath: keyPath].cs_keyPathString, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,46 +407,10 @@ extension SelectTerm where O: CoreStoreObject {
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
*/
|
||||
public static func count<V>(_ keyPath: KeyPath<O,
|
||||
ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
public static func count<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O,
|
||||
K>, as alias: KeyPathString? = nil) -> SelectTerm<O> where K.ObjectType == O {
|
||||
|
||||
return self.count(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for a count query.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
*/
|
||||
public static func count<V>(_ keyPath: KeyPath<O,
|
||||
ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.count(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for a count query.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
*/
|
||||
public static func count<V>(_ keyPath: KeyPath<O,
|
||||
TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.count(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for a count query.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
*/
|
||||
public static func count<V>(_ keyPath: KeyPath<O,
|
||||
TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.count(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
return self.count(O.meta[keyPath: keyPath].cs_keyPathString, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -518,46 +419,10 @@ extension SelectTerm where O: CoreStoreObject {
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
*/
|
||||
public static func maximum<V>(_ keyPath: KeyPath<O,
|
||||
ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
public static func maximum<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O,
|
||||
K>, as alias: KeyPathString? = nil) -> SelectTerm<O> where K.ObjectType == O {
|
||||
|
||||
return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
*/
|
||||
public static func maximum<V>(_ keyPath: KeyPath<O,
|
||||
ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
*/
|
||||
public static func maximum<V>(_ keyPath: KeyPath<O,
|
||||
TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
*/
|
||||
public static func maximum<V>(_ keyPath: KeyPath<O,
|
||||
TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.maximum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
return self.maximum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -566,42 +431,9 @@ extension SelectTerm where O: CoreStoreObject {
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
*/
|
||||
public static func minimum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
public static func minimum<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>, as alias: KeyPathString? = nil) -> SelectTerm<O> where K.ObjectType == O {
|
||||
|
||||
return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
*/
|
||||
public static func minimum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
*/
|
||||
public static func minimum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
*/
|
||||
public static func minimum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.minimum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
return self.minimum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -610,42 +442,9 @@ extension SelectTerm where O: CoreStoreObject {
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
*/
|
||||
public static func sum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
public static func sum<K: AttributeKeyPathStringConvertible>(_ keyPath: KeyPath<O, K>, as alias: KeyPathString? = nil) -> SelectTerm<O> where K.ObjectType == O {
|
||||
|
||||
return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
*/
|
||||
public static func sum<V>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
*/
|
||||
public static func sum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute.
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
*/
|
||||
public static func sum<V>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<V>>, as alias: KeyPathString? = nil) -> SelectTerm<O> {
|
||||
|
||||
return self.sum(O.meta[keyPath: keyPath].keyPath, as: alias)
|
||||
return self.sum(O.meta[keyPath: keyPath].cs_keyPathString, as: alias)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
303
Sources/Transformable.Optional.swift
Normal file
303
Sources/Transformable.Optional.swift
Normal file
@@ -0,0 +1,303 @@
|
||||
//
|
||||
// Transformable.Optional.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - TransformableContainer
|
||||
|
||||
extension TransformableContainer {
|
||||
|
||||
// MARK: - Optional
|
||||
|
||||
/**
|
||||
The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Optional<V: NSCoding & NSCopying>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let color = Transformable.Optional<UIColor>(
|
||||
"color",
|
||||
isTransient: true,
|
||||
customGetter: Animal.getColor(_:)
|
||||
)
|
||||
}
|
||||
|
||||
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor? {
|
||||
if let cachedColor = partialObject.primitiveValue(for: { $0.color }) {
|
||||
return cachedColor
|
||||
}
|
||||
let color: UIColor?
|
||||
switch partialObject.value(for: { $0.species }) {
|
||||
|
||||
case "Swift": color = UIColor.orange
|
||||
case "Bulbasaur": color = UIColor.green
|
||||
default: return nil
|
||||
}
|
||||
partialObject.setPrimitiveValue(color, for: { $0.color })
|
||||
return color
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.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<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V? = nil,
|
||||
isTransient: Bool = false,
|
||||
allowsExternalBinaryDataStorage: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: .transformableAttributeType,
|
||||
isOptional: true,
|
||||
isTransient: isTransient,
|
||||
allowsExternalBinaryDataStorage: allowsExternalBinaryDataStorage,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial()
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return object.value(forKey: self.keyPath) as! V?
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType?
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
newValue as! V?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension TransformableContainer.Optional {
|
||||
|
||||
/**
|
||||
Assigns an optional transformable value to the property. The operation
|
||||
```
|
||||
animal.color .= UIColor.red
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = UIColor.red
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: TransformableContainer<O>.Optional<V>, _ newValue: V?) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns an optional transformable value from another property. The operation
|
||||
```
|
||||
animal.color .= anotherAnimal.color
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = anotherAnimal.color.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Optional<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a transformable value from another property. The operation
|
||||
```
|
||||
animal.color .= anotherAnimal.color
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = anotherAnimal.color.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
}
|
||||
291
Sources/Transformable.Required.swift
Normal file
291
Sources/Transformable.Required.swift
Normal file
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// Transformable.Required.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - TransformableContainer
|
||||
|
||||
extension TransformableContainer {
|
||||
|
||||
// MARK: - Required
|
||||
|
||||
/**
|
||||
The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Required<V: NSCoding & NSCopying>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let color = Transformable.Required<UIColor>(
|
||||
"color",
|
||||
initial: UIColor.clear,
|
||||
isTransient: true,
|
||||
customGetter: Animal.getColor(_:)
|
||||
)
|
||||
}
|
||||
|
||||
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor {
|
||||
let cachedColor = partialObject.primitiveValue(for: { $0.color })
|
||||
if cachedColor != UIColor.clear {
|
||||
|
||||
return cachedColor
|
||||
}
|
||||
let color: UIColor
|
||||
switch partialObject.value(for: { $0.species }) {
|
||||
|
||||
case "Swift": color = UIColor.orange
|
||||
case "Bulbasaur": color = UIColor.green
|
||||
default: color = UIColor.black
|
||||
}
|
||||
partialObject.setPrimitiveValue(color, for: { $0.color })
|
||||
return color
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.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<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V,
|
||||
isTransient: Bool = false,
|
||||
allowsExternalBinaryDataStorage: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: .transformableAttributeType,
|
||||
isOptional: false,
|
||||
isTransient: isTransient,
|
||||
allowsExternalBinaryDataStorage: allowsExternalBinaryDataStorage,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial()
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return object.value(forKey: self.keyPath)! as! V
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
newValue as! V
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension TransformableContainer.Required {
|
||||
|
||||
/**
|
||||
Assigns a transformable value to the property. The operation
|
||||
```
|
||||
animal.color .= UIColor.red
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = UIColor.red
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: TransformableContainer<O>.Required<V>, _ newValue: V) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a transformable value from another property. The operation
|
||||
```
|
||||
animal.nickname .= anotherAnimal.species
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = anotherAnimal.species.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: TransformableContainer<O>.Required<V>, _ property2: TransformableContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
}
|
||||
@@ -58,538 +58,4 @@ extension DynamicObject where Self: CoreStoreObject {
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum TransformableContainer<O: CoreStoreObject> {
|
||||
|
||||
// MARK: - Required
|
||||
|
||||
/**
|
||||
The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Required<V: NSCoding & NSCopying>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let color = Transformable.Required<UIColor>(
|
||||
"color",
|
||||
initial: UIColor.clear,
|
||||
isTransient: true,
|
||||
customGetter: Animal.getColor(_:)
|
||||
)
|
||||
}
|
||||
|
||||
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor {
|
||||
let cachedColor = partialObject.primitiveValue(for: { $0.color })
|
||||
if cachedColor != UIColor.clear {
|
||||
|
||||
return cachedColor
|
||||
}
|
||||
let color: UIColor
|
||||
switch partialObject.value(for: { $0.species }) {
|
||||
|
||||
case "Swift": color = UIColor.orange
|
||||
case "Bulbasaur": color = UIColor.green
|
||||
default: color = UIColor.black
|
||||
}
|
||||
partialObject.setPrimitiveValue(color, for: { $0.color })
|
||||
return color
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.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<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V,
|
||||
isTransient: Bool = false,
|
||||
allowsExternalBinaryDataStorage: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.defaultValue = initial
|
||||
self.isTransient = isTransient
|
||||
self.allowsExternalBinaryDataStorage = allowsExternalBinaryDataStorage
|
||||
self.versionHashModifier = versionHashModifier
|
||||
self.renamingIdentifier = renamingIdentifier
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
self.affectedByKeyPaths = affectedByKeyPaths
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return object.value(forKey: self.keyPath)! as! V
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal static var attributeType: NSAttributeType {
|
||||
|
||||
return .transformableAttributeType
|
||||
}
|
||||
|
||||
internal let isOptional = false
|
||||
internal let isTransient: Bool
|
||||
internal let allowsExternalBinaryDataStorage: Bool
|
||||
internal let versionHashModifier: () -> String?
|
||||
internal let renamingIdentifier: () -> String?
|
||||
internal let defaultValue: () -> Any?
|
||||
internal let affectedByKeyPaths: () -> Set<String>
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
newValue as! V
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Optional
|
||||
|
||||
/**
|
||||
The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Optional<V: NSCoding & NSCopying>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let color = Transformable.Optional<UIColor>(
|
||||
"color",
|
||||
isTransient: true,
|
||||
customGetter: Animal.getColor(_:)
|
||||
)
|
||||
}
|
||||
|
||||
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor? {
|
||||
if let cachedColor = partialObject.primitiveValue(for: { $0.color }) {
|
||||
return cachedColor
|
||||
}
|
||||
let color: UIColor?
|
||||
switch partialObject.value(for: { $0.species }) {
|
||||
|
||||
case "Swift": color = UIColor.orange
|
||||
case "Bulbasaur": color = UIColor.green
|
||||
default: return nil
|
||||
}
|
||||
partialObject.setPrimitiveValue(color, for: { $0.color })
|
||||
return color
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter allowsExternalBinaryDataStorage: `true` if the attribute allows external binary storage, otherwise `false`.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.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<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V? = nil,
|
||||
isTransient: Bool = false,
|
||||
allowsExternalBinaryDataStorage: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.defaultValue = initial
|
||||
self.isTransient = isTransient
|
||||
self.allowsExternalBinaryDataStorage = allowsExternalBinaryDataStorage
|
||||
self.versionHashModifier = versionHashModifier
|
||||
self.renamingIdentifier = renamingIdentifier
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
self.affectedByKeyPaths = affectedByKeyPaths
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return object.value(forKey: self.keyPath) as! V?
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
object.setValue(
|
||||
newValue,
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType?
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal static var attributeType: NSAttributeType {
|
||||
|
||||
return .transformableAttributeType
|
||||
}
|
||||
|
||||
internal let isOptional = true
|
||||
internal let isTransient: Bool
|
||||
internal let allowsExternalBinaryDataStorage: Bool
|
||||
internal let versionHashModifier: () -> String?
|
||||
internal let renamingIdentifier: () -> String?
|
||||
internal let defaultValue: () -> Any?
|
||||
internal let affectedByKeyPaths: () -> Set<String>
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
newValue as! V?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension TransformableContainer.Required {
|
||||
|
||||
/**
|
||||
Assigns a transformable value to the property. The operation
|
||||
```
|
||||
animal.color .= UIColor.red
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = UIColor.red
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: TransformableContainer<O>.Required<V>, _ newValue: V) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a transformable value from another property. The operation
|
||||
```
|
||||
animal.nickname .= anotherAnimal.species
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = anotherAnimal.species.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: TransformableContainer<O>.Required<V>, _ property2: TransformableContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
}
|
||||
|
||||
extension TransformableContainer.Optional {
|
||||
|
||||
/**
|
||||
Assigns an optional transformable value to the property. The operation
|
||||
```
|
||||
animal.color .= UIColor.red
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = UIColor.red
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: TransformableContainer<O>.Optional<V>, _ newValue: V?) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns an optional transformable value from another property. The operation
|
||||
```
|
||||
animal.color .= anotherAnimal.color
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = anotherAnimal.color.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Optional<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a transformable value from another property. The operation
|
||||
```
|
||||
animal.color .= anotherAnimal.color
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.color.value = anotherAnimal.color.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
}
|
||||
public enum TransformableContainer<O: CoreStoreObject> {}
|
||||
|
||||
364
Sources/Value.Optional.swift
Normal file
364
Sources/Value.Optional.swift
Normal file
@@ -0,0 +1,364 @@
|
||||
//
|
||||
// Value.Optional.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - ValueContainer
|
||||
|
||||
extension ValueContainer {
|
||||
|
||||
// MARK: - Optional
|
||||
|
||||
/**
|
||||
The containing type for optional value properties. Any type that conforms to `ImportableAttributeType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Value.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Optional<V: ImportableAttributeType>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
let title = Value.Optional<String>("title", initial: "Mr.")
|
||||
let name = Value.Optional<String>("name")
|
||||
let displayName = Value.Optional<String>(
|
||||
"displayName",
|
||||
isTransient: true,
|
||||
customGetter: Person.getName(_:)
|
||||
)
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String? {
|
||||
if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created. Defaults to `nil` if not specified.
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter.
|
||||
- parameter self: the `CoreStoreObject`
|
||||
- parameter getValue: the original getter for the property
|
||||
- parameter customSetter: use this closure to make final transformations to the new value before assigning to the property.
|
||||
- parameter setValue: the original setter for the property
|
||||
- parameter finalNewValue: the transformed new value
|
||||
- parameter originalNewValue: the original new value
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V? = nil,
|
||||
isTransient: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: V.cs_rawAttributeType,
|
||||
isOptional: true,
|
||||
isTransient: isTransient,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial()?.cs_toQueryableNativeType()
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return (object.value(forKey: self.keyPath) as! V.QueryableNativeType?)
|
||||
.flatMap(V.cs_fromQueryableNativeType)
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
object.setValue(
|
||||
newValue?.cs_toQueryableNativeType(),
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType?
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value?.cs_toQueryableNativeType()
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
(newValue as! V.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension ValueContainer.Optional {
|
||||
|
||||
/**
|
||||
Assigns an optional value to the property. The operation
|
||||
```
|
||||
animal.nickname .= "Taylor"
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = "Taylor"
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: ValueContainer<O>.Optional<V>, _ newValue: V?) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns an optional value from another property. The operation
|
||||
```
|
||||
animal.nickname .= anotherAnimal.nickname
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = anotherAnimal.nickname.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O2>.Optional<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a value from another property. The operation
|
||||
```
|
||||
animal.nickname .= anotherAnimal.species
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = anotherAnimal.species.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another value
|
||||
```
|
||||
if animal.species .== "Swift" { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == "Swift" { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Optional<V>, _ value: V?) -> Bool {
|
||||
|
||||
return property.value == value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if "Swift" .== animal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if "Swift" == animal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ value: V?, _ property: ValueContainer<O>.Optional<V>) -> Bool {
|
||||
|
||||
return value == property.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O>.Optional<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O>.Required<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
}
|
||||
347
Sources/Value.Required.swift
Normal file
347
Sources/Value.Required.swift
Normal file
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// Value.Required.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2020 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - ValueContainer
|
||||
|
||||
extension ValueContainer {
|
||||
|
||||
// MARK: - Required
|
||||
|
||||
/**
|
||||
The containing type for required value properties. Any type that conforms to `ImportableAttributeType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Value.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Required<V: ImportableAttributeType>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
let title = Value.Required<String>("title", initial: "Mr.")
|
||||
let name = Value.Required<String>("name", initial: "")
|
||||
let displayName = Value.Required<String>(
|
||||
"displayName",
|
||||
initial: "",
|
||||
isTransient: true,
|
||||
customGetter: Person.getName(_:)
|
||||
)
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String {
|
||||
let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName })
|
||||
if !cachedDisplayName.isEmpty {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.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<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V,
|
||||
isTransient: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.entityDescriptionValues = {
|
||||
(
|
||||
attributeType: V.cs_rawAttributeType,
|
||||
isOptional: false,
|
||||
isTransient: isTransient,
|
||||
allowsExternalBinaryDataStorage: false,
|
||||
versionHashModifier: versionHashModifier(),
|
||||
renamingIdentifier: renamingIdentifier(),
|
||||
affectedByKeyPaths: affectedByKeyPaths(),
|
||||
defaultValue: initial().cs_toQueryableNativeType()
|
||||
)
|
||||
}
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return V.cs_fromQueryableNativeType(
|
||||
object.value(forKey: self.keyPath)! as! V.QueryableNativeType
|
||||
)!
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
return object.setValue(
|
||||
newValue.cs_toQueryableNativeType(),
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal let entityDescriptionValues: () -> AttributeProtocol.EntityDescriptionValues
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value.cs_toQueryableNativeType()
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
V.cs_fromQueryableNativeType(newValue as! V.QueryableNativeType)!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension ValueContainer.Required {
|
||||
|
||||
/**
|
||||
Assigns a value to the property. The operation
|
||||
```
|
||||
animal.species .= "Swift"
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.species.value = "Swift"
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: ValueContainer<O>.Required<V>, _ newValue: V) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a value from another property. The operation
|
||||
```
|
||||
animal.species .= anotherAnimal.species
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.species.value = anotherAnimal.species.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: ValueContainer<O>.Required<V>, _ property2: ValueContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another value
|
||||
```
|
||||
if animal.species .== "Swift" { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == "Swift" { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Required<V>, _ value: V?) -> Bool {
|
||||
|
||||
return property.value == value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a value and a property's value
|
||||
```
|
||||
if "Swift" .== animal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if "Swift" == animal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ value: V?, _ property: ValueContainer<O>.Required<V>) -> Bool {
|
||||
|
||||
return value == property.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Required<V>, _ property2: ValueContainer<O>.Required<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Required<V>, _ property2: ValueContainer<O>.Optional<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
}
|
||||
@@ -58,653 +58,4 @@ extension DynamicObject where Self: CoreStoreObject {
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum ValueContainer<O: CoreStoreObject> {
|
||||
|
||||
// MARK: - Required
|
||||
|
||||
/**
|
||||
The containing type for required value properties. Any type that conforms to `ImportableAttributeType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Value.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Required<V: ImportableAttributeType>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
let title = Value.Required<String>("title", initial: "Mr.")
|
||||
let name = Value.Required<String>("name", initial: "")
|
||||
let displayName = Value.Required<String>(
|
||||
"displayName",
|
||||
initial: "",
|
||||
isTransient: true,
|
||||
customGetter: Person.getName(_:)
|
||||
)
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String {
|
||||
let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName })
|
||||
if !cachedDisplayName.isEmpty {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, 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<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.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<O>`, 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<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V,
|
||||
isTransient: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.isTransient = isTransient
|
||||
self.defaultValue = { initial().cs_toQueryableNativeType() }
|
||||
self.versionHashModifier = versionHashModifier
|
||||
self.renamingIdentifier = renamingIdentifier
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
self.affectedByKeyPaths = affectedByKeyPaths
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return V.cs_fromQueryableNativeType(
|
||||
object.value(forKey: self.keyPath)! as! V.QueryableNativeType
|
||||
)!
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
return object.setValue(
|
||||
newValue.cs_toQueryableNativeType(),
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal static var attributeType: NSAttributeType {
|
||||
|
||||
return V.cs_rawAttributeType
|
||||
}
|
||||
|
||||
internal let isOptional = false
|
||||
internal let isTransient: Bool
|
||||
internal let allowsExternalBinaryDataStorage = false
|
||||
internal let versionHashModifier: () -> String?
|
||||
internal let renamingIdentifier: () -> String?
|
||||
internal let defaultValue: () -> Any?
|
||||
internal let affectedByKeyPaths: () -> Set<String>
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value.cs_toQueryableNativeType()
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
V.cs_fromQueryableNativeType(newValue as! V.QueryableNativeType)!
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Optional
|
||||
|
||||
/**
|
||||
The containing type for optional value properties. Any type that conforms to `ImportableAttributeType` are supported.
|
||||
```
|
||||
class Animal: CoreStoreObject {
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let color = Transformable.Optional<UIColor>("color")
|
||||
}
|
||||
```
|
||||
- Important: `Value.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
|
||||
*/
|
||||
public final class Optional<V: ImportableAttributeType>: AttributeKeyPathStringConvertible, AttributeProtocol {
|
||||
|
||||
/**
|
||||
Initializes the metadata for the property.
|
||||
```
|
||||
class Person: CoreStoreObject {
|
||||
let title = Value.Optional<String>("title", initial: "Mr.")
|
||||
let name = Value.Optional<String>("name")
|
||||
let displayName = Value.Optional<String>(
|
||||
"displayName",
|
||||
isTransient: true,
|
||||
customGetter: Person.getName(_:)
|
||||
)
|
||||
|
||||
private static func getName(_ partialObject: PartialObject<Person>) -> String? {
|
||||
if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) {
|
||||
return cachedDisplayName
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
```
|
||||
- parameter keyPath: the permanent attribute name for this property.
|
||||
- parameter initial: the initial value for the property when the object is first created. Defaults to `nil` if not specified.
|
||||
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
|
||||
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
|
||||
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
|
||||
- parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter.
|
||||
- parameter self: the `CoreStoreObject`
|
||||
- parameter getValue: the original getter for the property
|
||||
- parameter customSetter: use this closure to make final transformations to the new value before assigning to the property.
|
||||
- parameter setValue: the original setter for the property
|
||||
- parameter finalNewValue: the transformed new value
|
||||
- parameter originalNewValue: the original new value
|
||||
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
|
||||
*/
|
||||
public init(
|
||||
_ keyPath: KeyPathString,
|
||||
initial: @autoclosure @escaping () -> V? = nil,
|
||||
isTransient: Bool = false,
|
||||
versionHashModifier: @autoclosure @escaping () -> String? = nil,
|
||||
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
|
||||
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
|
||||
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
|
||||
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
|
||||
|
||||
self.keyPath = keyPath
|
||||
self.isTransient = isTransient
|
||||
self.defaultValue = { initial()?.cs_toQueryableNativeType() }
|
||||
self.versionHashModifier = versionHashModifier
|
||||
self.renamingIdentifier = renamingIdentifier
|
||||
self.customGetter = customGetter
|
||||
self.customSetter = customSetter
|
||||
self.affectedByKeyPaths = affectedByKeyPaths
|
||||
}
|
||||
|
||||
/**
|
||||
The attribute value
|
||||
*/
|
||||
public var value: ReturnValueType {
|
||||
|
||||
get {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
if let customGetter = self.customGetter {
|
||||
|
||||
return customGetter(PartialObject<O>(object))
|
||||
}
|
||||
return (object.value(forKey: self.keyPath) as! V.QueryableNativeType?)
|
||||
.flatMap(V.cs_fromQueryableNativeType)
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
Internals.assert(
|
||||
self.rawObject != nil,
|
||||
"Attempted to access values from a \(Internals.typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
|
||||
)
|
||||
return withExtendedLifetime(self.rawObject!) { (object) in
|
||||
|
||||
Internals.assert(
|
||||
object.isRunningInAllowedQueue() == true,
|
||||
"Attempted to access \(Internals.typeName(O.self))'s value outside it's designated queue."
|
||||
)
|
||||
Internals.assert(
|
||||
object.isEditableInContext() == true,
|
||||
"Attempted to update a \(Internals.typeName(O.self))'s value from outside a transaction."
|
||||
)
|
||||
if let customSetter = self.customSetter {
|
||||
|
||||
return customSetter(PartialObject<O>(object), newValue)
|
||||
}
|
||||
object.setValue(
|
||||
newValue?.cs_toQueryableNativeType(),
|
||||
forKey: self.keyPath
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: AnyKeyPathStringConvertible
|
||||
|
||||
public var cs_keyPathString: String {
|
||||
|
||||
return self.keyPath
|
||||
}
|
||||
|
||||
|
||||
// MARK: KeyPathStringConvertible
|
||||
|
||||
public typealias ObjectType = O
|
||||
public typealias DestinationValueType = V
|
||||
|
||||
|
||||
// MARK: AttributeKeyPathStringConvertible
|
||||
|
||||
public typealias ReturnValueType = DestinationValueType?
|
||||
|
||||
|
||||
// MARK: PropertyProtocol
|
||||
|
||||
internal let keyPath: KeyPathString
|
||||
|
||||
|
||||
// MARK: AttributeProtocol
|
||||
|
||||
internal static var attributeType: NSAttributeType {
|
||||
|
||||
return V.cs_rawAttributeType
|
||||
}
|
||||
|
||||
internal let isOptional = true
|
||||
internal let isTransient: Bool
|
||||
internal let allowsExternalBinaryDataStorage = false
|
||||
internal let versionHashModifier: () -> String?
|
||||
internal let renamingIdentifier: () -> String?
|
||||
internal let defaultValue: () -> Any?
|
||||
internal let affectedByKeyPaths: () -> Set<String>
|
||||
internal var rawObject: CoreStoreManagedObject?
|
||||
|
||||
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customGetter = self.customGetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any) -> Any? in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willAccessValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didAccessValue(forKey: keyPath)
|
||||
}
|
||||
let value = customGetter(PartialObject<O>(rawObject))
|
||||
return value?.cs_toQueryableNativeType()
|
||||
}
|
||||
}
|
||||
|
||||
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = Internals.with { [unowned self] in
|
||||
|
||||
guard let customSetter = self.customSetter else {
|
||||
|
||||
return nil
|
||||
}
|
||||
let keyPath = self.keyPath
|
||||
return { (_ id: Any, _ newValue: Any?) -> Void in
|
||||
|
||||
let rawObject = id as! CoreStoreManagedObject
|
||||
rawObject.willChangeValue(forKey: keyPath)
|
||||
defer {
|
||||
|
||||
rawObject.didChangeValue(forKey: keyPath)
|
||||
}
|
||||
customSetter(
|
||||
PartialObject<O>(rawObject),
|
||||
(newValue as! V.QueryableNativeType?).flatMap(V.cs_fromQueryableNativeType)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal var valueForSnapshot: Any? {
|
||||
|
||||
return self.value
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
|
||||
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Operations
|
||||
|
||||
infix operator .= : AssignmentPrecedence
|
||||
infix operator .== : ComparisonPrecedence
|
||||
|
||||
extension ValueContainer.Required {
|
||||
|
||||
/**
|
||||
Assigns a value to the property. The operation
|
||||
```
|
||||
animal.species .= "Swift"
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.species.value = "Swift"
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: ValueContainer<O>.Required<V>, _ newValue: V) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a value from another property. The operation
|
||||
```
|
||||
animal.species .= anotherAnimal.species
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.species.value = anotherAnimal.species.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: ValueContainer<O>.Required<V>, _ property2: ValueContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another value
|
||||
```
|
||||
if animal.species .== "Swift" { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == "Swift" { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Required<V>, _ value: V?) -> Bool {
|
||||
|
||||
return property.value == value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a value and a property's value
|
||||
```
|
||||
if "Swift" .== animal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if "Swift" == animal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ value: V?, _ property: ValueContainer<O>.Required<V>) -> Bool {
|
||||
|
||||
return value == property.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Required<V>, _ property2: ValueContainer<O>.Required<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Required<V>, _ property2: ValueContainer<O>.Optional<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
}
|
||||
|
||||
extension ValueContainer.Optional {
|
||||
|
||||
/**
|
||||
Assigns an optional value to the property. The operation
|
||||
```
|
||||
animal.nickname .= "Taylor"
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = "Taylor"
|
||||
```
|
||||
*/
|
||||
public static func .= (_ property: ValueContainer<O>.Optional<V>, _ newValue: V?) {
|
||||
|
||||
property.value = newValue
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns an optional value from another property. The operation
|
||||
```
|
||||
animal.nickname .= anotherAnimal.nickname
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = anotherAnimal.nickname.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O2>.Optional<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Assigns a value from another property. The operation
|
||||
```
|
||||
animal.nickname .= anotherAnimal.species
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
animal.nickname.value = anotherAnimal.species.value
|
||||
```
|
||||
*/
|
||||
public static func .= <O2>(_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O2>.Required<V>) {
|
||||
|
||||
property.value = property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another value
|
||||
```
|
||||
if animal.species .== "Swift" { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == "Swift" { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Optional<V>, _ value: V?) -> Bool {
|
||||
|
||||
return property.value == value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if "Swift" .== animal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if "Swift" == animal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ value: V?, _ property: ValueContainer<O>.Optional<V>) -> Bool {
|
||||
|
||||
return value == property.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O>.Optional<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
|
||||
/**
|
||||
Compares equality between a property's value and another property's value
|
||||
```
|
||||
if animal.species .== anotherAnimal.species { ... }
|
||||
```
|
||||
is equivalent to
|
||||
```
|
||||
if animal.species.value == anotherAnimal.species.value { ... }
|
||||
```
|
||||
*/
|
||||
public static func .== (_ property: ValueContainer<O>.Optional<V>, _ property2: ValueContainer<O>.Required<V>) -> Bool {
|
||||
|
||||
return property.value == property2.value
|
||||
}
|
||||
}
|
||||
public enum ValueContainer<O: CoreStoreObject> {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -235,6 +235,20 @@ public func ~ <O: NSManagedObject, D: NSManagedObject, T, C: AllowedObjectiveCTo
|
||||
|
||||
// MARK: - ~ where D: CoreStoreObject
|
||||
|
||||
/**
|
||||
Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions
|
||||
```
|
||||
let owner = dataStack.fetchOne(From<Pet>().where((\.$master ~ \.$name) == "John"))
|
||||
```
|
||||
*/
|
||||
public func ~ <O: CoreStoreObject, D: FieldRelationshipToOneType, K: KeyPathStringConvertible>(_ lhs: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ rhs: KeyPath<D.DestinationObjectType, K>) -> Where<O>.Expression<Where<O>.SingleTarget, K.DestinationValueType> where K.ObjectType == D.DestinationObjectType {
|
||||
|
||||
return .init(
|
||||
O.meta[keyPath: lhs].cs_keyPathString,
|
||||
D.DestinationObjectType.meta[keyPath: rhs].cs_keyPathString
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions
|
||||
```
|
||||
@@ -277,6 +291,20 @@ public func ~ <O: CoreStoreObject, D: CoreStoreObject, T, K: KeyPathStringConver
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions
|
||||
```
|
||||
let happyPets = dataStack.fetchAll(From<Pet>().where((\.$master ~ \.$pets).count() > 1))
|
||||
```
|
||||
*/
|
||||
public func ~ <O: CoreStoreObject, D: FieldRelationshipToOneType, K: ToManyRelationshipKeyPathStringConvertible>(_ lhs: KeyPath<O, FieldContainer<O>.Relationship<D>>, _ rhs: KeyPath<D.DestinationObjectType, K>) -> Where<O>.Expression<Where<O>.CollectionTarget, K.DestinationValueType> where K.ObjectType == D.DestinationObjectType {
|
||||
|
||||
return .init(
|
||||
O.meta[keyPath: lhs].cs_keyPathString,
|
||||
D.DestinationObjectType.meta[keyPath: rhs].cs_keyPathString
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Connects multiple `KeyPathStringConvertible`s to create a type-safe chain usable in query/fetch expressions
|
||||
```
|
||||
|
||||
@@ -174,21 +174,41 @@ public struct Where<O: DynamicObject>: 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<U: QueryableAttributeType>(_ keyPath: KeyPathString, isEqualTo value: U?) {
|
||||
|
||||
public init<V: FieldStorableType>(_ 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<U: QueryableAttributeType>(_ 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<O: DynamicObject>: 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<S: Sequence>(_ 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<O: DynamicObject>: WhereClauseType, FetchClause, QueryClause
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the sequence to check membership of
|
||||
*/
|
||||
@_disfavoredOverload
|
||||
public init<S: Sequence>(_ 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,28 @@ 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<V>(_ keyPath: KeyPath<O, FieldContainer<O>.Stored<V>>, isEqualTo value: V) {
|
||||
|
||||
self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter value: the arguments for the `==` operator
|
||||
*/
|
||||
public init<V: FieldRelationshipToOneType>(_ keyPath: KeyPath<O, FieldContainer<O>.Relationship<V>>, isEqualTo value: V.DestinationObjectType?) {
|
||||
|
||||
self.init(O.meta[keyPath: keyPath].keyPath, isEqualTo: value)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality to `nil`
|
||||
|
||||
Reference in New Issue
Block a user