Compare commits

...

27 Commits
7.0.1 ... 7.0.4

Author SHA1 Message Date
John Estropia
8cb8b95c2e fix build for watchOS 2020-02-08 08:49:11 +09:00
John Estropia
5e37ee4566 Reorganize properties source files 2020-01-10 17:04:51 +09:00
John Estropia
c544e0cce8 lazily evaluate NSEntityDescription-required fields from CoreStoreObject attributes 2020-01-09 17:00:43 +09:00
John Estropia
f119a3adec add SPM installation instructions to README 2020-01-08 11:21:41 +09:00
John Estropia
c951cb87a3 version bump 2020-01-08 11:03:43 +09:00
John Estropia
08147806a0 explicit exclusion of objc files in package.swift 2020-01-08 11:01:22 +09:00
John Estropia
4beb11519e Deprecation of ObjectiveC shivs 2020-01-08 10:26:27 +09:00
John Estropia
b7ebda4487 Use generic collection types in ListSnapshot mutators 2020-01-05 02:37:42 +09:00
John Estropia
b4489301ac fix SPM spec 2019-12-23 11:53:46 +09:00
John Estropia
c025e5acc6 version bump 2019-12-23 10:11:26 +09:00
John Estropia
57745f36a8 Allow purging of datasource 2019-12-17 21:10:01 +09:00
John Estropia
eef1c99f11 Allow custom views to consume ListSnapshot diffable data 2019-12-17 19:45:53 +09:00
John Estropia
9a19919392 add utility to create ObjectPublisher directly from a DynamicObject using its own context 2019-12-02 12:21:06 +09:00
John Estropia
3e2d62fe67 missed public modifier 2019-12-02 11:53:23 +09:00
John Estropia
6f275eb63a add "updatedItemIdentifiers" utility to ListSnapshot 2019-12-02 10:52:36 +09:00
John Estropia
b12dba4d15 optimizations 2019-11-29 20:09:43 +09:00
John Estropia
4ee1b04523 Support for fetchOffset in ListPublisher, optimize slicing logic 2019-11-18 19:52:57 +09:00
John Estropia
b1decc9853 Force fetchLimit for ListPublisher and ListSnapshot 2019-11-14 20:34:48 +09:00
John Estropia
c2e4c033ef Merge pull request #348 from ntnmrndn/fixWarning
Fix warning
2019-11-05 19:11:51 +09:00
John Estropia
e12223df85 fix casting error 2019-10-29 20:37:08 +09:00
John Estropia
468922d5ed fix casting issues 2019-10-29 20:30:03 +09:00
John Estropia
6b9a4b480b minor 2019-10-29 20:18:14 +09:00
Antoine Marandon
81b482e28b Fix warning 2019-10-29 17:27:25 +09:00
John Estropia
c112a84c0a Add debugDescription implementation for new Publisher and Snapshot types 2019-10-28 19:31:02 +09:00
John Estropia
88ab0b5e15 provide direct conversion from DynamicObject to ObjectSnapshot 2019-10-28 12:03:17 +09:00
John Estropia
717cb75720 Add utility to fetch ObjectPublishers by ObjectID 2019-10-28 11:23:12 +09:00
John Estropia
998938490c Make ObjectPublishers even lighter by lazy-loading observers 2019-10-25 19:16:38 +09:00
60 changed files with 4528 additions and 3644 deletions

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "7.0.1"
s.version = "7.0.4"
s.swift_version = "5.1"
s.license = "MIT"
s.homepage = "https://github.com/JohnEstropia/CoreStore"

View File

@@ -105,6 +105,34 @@
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
B509D7BA23C846E300F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
B509D7BC23C847BC00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
B509D7BD23C8480A00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
B509D7BE23C8480A00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
B509D7BF23C8480B00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
B509D7C023C8480B00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
B509D7C123C8480B00F42824 /* Value.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7B923C846E300F42824 /* Value.Required.swift */; };
B509D7C223C8480B00F42824 /* Value.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7BB23C847BC00F42824 /* Value.Optional.swift */; };
B509D7C423C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */; };
B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
B509D7CA23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
B509D7CB23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
B509D7CC23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */; };
B509D7CE23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
B509D7CF23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
B509D7D023C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
B509D7D123C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */; };
B509D7D323C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D223C84E1900F42824 /* Transformable.Required.swift */; };
B509D7D823C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509D7D723C84E2600F42824 /* Transformable.Optional.swift */; };
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
@@ -141,6 +169,13 @@
B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */; };
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */; };
B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */; };
B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */; };
B51FE5AB1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */; };
@@ -465,10 +500,10 @@
B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */; };
B56321B41BD6521C006C9394 /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; };
B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* Internals.WeakObject.swift */; };
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */; };
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */; };
B56507941D3930BC000596DA /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507931D3930BC000596DA /* CoreData.framework */; };
B56507961D3930C1000596DA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507951D3930C1000596DA /* Foundation.framework */; };
B56507981D3930CC000596DA /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B56507971D3930CC000596DA /* CoreData.framework */; };
@@ -583,10 +618,10 @@
B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; };
B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; };
B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A9921E1EA898710091A2E3 /* UserInfo.swift */; };
B5AA37F1235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
B5AA37F2235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
B5AA37F3235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
B5AA37F4235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */; };
B5AA37F1235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
B5AA37F2235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
B5AA37F3235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */; };
B5AA37FD235C3D1A00FFD4B9 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5AA37FC235C3D1A00FFD4B9 /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
B5AEFAB51C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; };
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */; };
@@ -600,10 +635,10 @@
B5BF7FB3234C97910070E741 /* DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */; };
B5BF7FB4234C97910070E741 /* DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */; };
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */; };
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */; };
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */; };
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
@@ -668,6 +703,7 @@
B5D33A041E96012400C880DE /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D33A001E96012400C880DE /* Relationship.swift */; };
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; };
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */; };
B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
@@ -897,6 +933,13 @@
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>"; };
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>"; };
@@ -906,6 +949,7 @@
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>"; };
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.EntityIdentifier.swift; sourceTree = "<group>"; };
B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.Target.swift; sourceTree = "<group>"; };
B51B5C2A22D43931009FA3BA /* String+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+KeyPaths.swift"; sourceTree = "<group>"; };
B51B5C2C22D43E38009FA3BA /* KeyPath+KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyPath+KeyPaths.swift"; sourceTree = "<group>"; };
B51FE5AA1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+CustomDebugStringConvertible.swift"; sourceTree = "<group>"; };
@@ -976,7 +1020,7 @@
B563216F1BD65082006C9394 /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B56321791BD650DE006C9394 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
B563217B1BD650E3006C9394 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS2.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.CollectionView-UIKit.swift"; sourceTree = "<group>"; };
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.CollectionViewAdapter-UIKit.swift"; sourceTree = "<group>"; };
B56507931D3930BC000596DA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.0.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
B56507951D3930C1000596DA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
B56507971D3930CC000596DA /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS3.0.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
@@ -1014,14 +1058,14 @@
B5A80DF52212C1BC006096AA /* Playground_iOS.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground_iOS.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionLock.swift; sourceTree = "<group>"; };
B5A9921E1EA898710091A2E3 /* UserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = "<group>"; };
B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "diffableDataSource.CollectionView-AppKit.swift"; sourceTree = "<group>"; };
B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.CollectionViewAdapter-AppKit.swift"; sourceTree = "<group>"; };
B5AA37FA235C3D1300FFD4B9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
B5AA37FC235C3D1A00FFD4B9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS6.0.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
B5AD60CD1C90141E00F2B2E8 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; };
B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = "<group>"; };
B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataSourceSnapshot.swift; sourceTree = "<group>"; };
B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.swift; sourceTree = "<group>"; };
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.TableView-UIKit.swift"; sourceTree = "<group>"; };
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.TableViewAdapter-UIKit.swift"; sourceTree = "<group>"; };
B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.swift; sourceTree = "<group>"; };
B5BF7FC0234D7B2E0070E741 /* ObjectPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisher.swift; sourceTree = "<group>"; };
B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = "<group>"; };
@@ -1042,6 +1086,7 @@
B5D33A001E96012400C880DE /* Relationship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = "<group>"; };
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.BaseAdapter.swift; sourceTree = "<group>"; };
B5D7A5B51CA3BF8F005C752B /* CSInto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSInto.swift; sourceTree = "<group>"; };
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+DataSources.swift"; sourceTree = "<group>"; };
B5D8CA7A2346EC550055D7D1 /* ListPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListPublisherTests.swift; sourceTree = "<group>"; };
@@ -1488,8 +1533,15 @@
isa = PBXGroup;
children = (
B5D33A001E96012400C880DE /* Relationship.swift */,
B509D7C323C848DA00F42824 /* Relationship.ToOne.swift */,
B509D7C823C8491C00F42824 /* Relationship.ToManyOrdered.swift */,
B509D7CD23C8492800F42824 /* Relationship.ToManyUnordered.swift */,
B5D339E11E948C3600C880DE /* Value.swift */,
B509D7B923C846E300F42824 /* Value.Required.swift */,
B509D7BB23C847BC00F42824 /* Value.Optional.swift */,
B5831B791F34ACBA00A9F647 /* Transformable.swift */,
B509D7D223C84E1900F42824 /* Transformable.Required.swift */,
B509D7D723C84E2600F42824 /* Transformable.Optional.swift */,
);
name = Properties;
sourceTree = "<group>";
@@ -1607,9 +1659,11 @@
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */,
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */,
B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */,
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift */,
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift */,
B5AA37F0235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift */,
B514EF0D23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift */,
B5D4A6B623A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift */,
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */,
B5635D132356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift */,
B5AA37F0235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift */,
);
name = DataSources;
sourceTree = "<group>";
@@ -2089,7 +2143,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
B509D7C423C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
B5CA2B081F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
@@ -2121,12 +2177,12 @@
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */,
B53FBA0B1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */,
B5AA37F1235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
B5AA37F1235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */,
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */,
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.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 */,
@@ -2151,7 +2207,7 @@
B56923F01EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */,
B546F9581C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */,
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
B5635D142356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */,
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */,
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
@@ -2162,8 +2218,10 @@
B5E84F111AFF847B0064E85B /* Select.swift in Sources */,
B51260931E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
B5DAFB482203D9F8003FCCD0 /* Where.Expression.swift in Sources */,
B509D7D823C84E2600F42824 /* Transformable.Optional.swift in Sources */,
B5FE4DA21C8481E100FA6A91 /* StorageInterface.swift in Sources */,
B5BF7FAD234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift in Sources */,
B509D7BA23C846E300F42824 /* Value.Required.swift in Sources */,
B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
B5DBE2D21C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */,
@@ -2182,6 +2240,7 @@
B5A1DAC81F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */,
B5ECDC111CA816E500C7F112 /* CSTweak.swift in Sources */,
B509D7D323C84E1900F42824 /* Transformable.Required.swift in Sources */,
B56923C41EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
B5E84F411AFF8CCD0064E85B /* TypeErasedClauses.swift in Sources */,
B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */,
@@ -2224,6 +2283,7 @@
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5215CA41FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
B514EF0E23A8BEEA0093DBA4 /* DiffableDataSource.Target.swift in Sources */,
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
B56923E41EB827F5007C4DC9 /* CustomSchemaMappingProvider.swift in Sources */,
@@ -2238,8 +2298,10 @@
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 */,
B5831B7A1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B5BF7FB2234C97910070E741 /* DiffableDataSource.swift in Sources */,
B546F9691C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
@@ -2260,6 +2322,7 @@
B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */,
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */,
B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
B509D7C923C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */,
B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B5277672234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
@@ -2316,7 +2379,7 @@
files = (
82BA18B61C4BBD3F00A0916E /* DataStack+Querying.swift in Sources */,
B5ECDBFB1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
B5635D152356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
B5CA2B091F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
B5C976E81C6E3A5D00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
B56923F61EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
@@ -2327,6 +2390,7 @@
82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */,
B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5D339D91E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
B509D7CF23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
82BA18CE1C4BBD7100A0916E /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
B56923FB1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
B55514EB1EED8BF900BAB888 /* From+Querying.swift in Sources */,
@@ -2369,6 +2433,7 @@
B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B55BB4DA23503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
B5E1B5951CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
B509D7CA23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
18166887232B9ED60097C275 /* String+KeyPaths.swift in Sources */,
B5ECDC2B1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
B56923F11EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
@@ -2387,6 +2452,7 @@
B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
B5DBE2D31C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
82BA18B41C4BBD3900A0916E /* BaseDataTransaction+Importing.swift in Sources */,
B514EF1223A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
B53FBA1A1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
82BA18CA1C4BBD5900A0916E /* MigrationResult.swift in Sources */,
B5519A5A1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
@@ -2428,6 +2494,7 @@
82BA18C31C4BBD5300A0916E /* ObjectObserver.swift in Sources */,
82BA18BF1C4BBD5300A0916E /* SectionBy.swift in Sources */,
B5D339ED1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
B509D7D423C84E1900F42824 /* Transformable.Required.swift in Sources */,
82BA18AC1C4BBD3100A0916E /* SynchronousDataTransaction.swift in Sources */,
82BA18C71C4BBD5900A0916E /* CoreStore+Migration.swift in Sources */,
B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
@@ -2435,6 +2502,7 @@
B5215CAA1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B50E175823517DE4004F033C /* Differentiable.swift in Sources */,
82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */,
B514EF0F23A8DB180093DBA4 /* DiffableDataSource.Target.swift in Sources */,
B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
@@ -2443,6 +2511,7 @@
82BA18D81C4BBD7100A0916E /* Internals.WeakObject.swift in Sources */,
B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B509D7BD23C8480A00F42824 /* Value.Required.swift in Sources */,
B5215CA51FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
B5D33A021E96012400C880DE /* Relationship.swift in Sources */,
B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */,
@@ -2463,6 +2532,7 @@
82BA18AD1C4BBD3100A0916E /* UnsafeDataTransaction.swift in Sources */,
B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B5277673234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */,
B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */,
82BA18A81C4BBD2900A0916E /* CoreStoreLogger.swift in Sources */,
B549F65F1E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
@@ -2474,7 +2544,7 @@
B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
18166884232B9ED00097C275 /* KeyPath+KeyPaths.swift in Sources */,
B5E8A72121C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
B5AA37F2235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
B5AA37F2235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
B501322B2346A9AE00FC238B /* ListPublisher.swift in Sources */,
@@ -2485,6 +2555,7 @@
82BA18B91C4BBD4A00A0916E /* From.swift in Sources */,
B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
82BA18BE1C4BBD4A00A0916E /* Tweak.swift in Sources */,
B509D7C523C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
@@ -2492,9 +2563,10 @@
B5D339E81E9493A500C880DE /* Entity.swift in Sources */,
B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
82BA18CC1C4BBD6400A0916E /* Progress+Convenience.swift in Sources */,
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
82BA18C01C4BBD5300A0916E /* DataStack+Observing.swift in Sources */,
82BA18A61C4BBD2900A0916E /* DefaultLogger.swift in Sources */,
B509D7BE23C8480A00F42824 /* Value.Optional.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2538,9 +2610,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */,
B5220E1E1D13080D009BC71E /* CSListMonitor.swift in Sources */,
B5AA37F4235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
B5DBE2D01C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
B5635D172356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
B5CA2B0B1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
B56923F81EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
B52DD1BE1BE1F94300949AFE /* Progress+Convenience.swift in Sources */,
@@ -2548,6 +2623,7 @@
B5ECDC151CA816E500C7F112 /* CSTweak.swift in Sources */,
B546F9761C9C553300D5AC55 /* SetupResult.swift in Sources */,
B53FBA161CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
B509D7D123C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
B5ECDC271CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5D339DB1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
@@ -2581,7 +2657,6 @@
B50564D62350CC3100482308 /* PropertyProtocol.swift in Sources */,
B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */,
B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */,
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */,
B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */,
@@ -2591,6 +2666,7 @@
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */,
B50EE14523473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B509D7CC23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
B55BB4D823503B9500C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */,
18166889232B9ED80097C275 /* String+KeyPaths.swift in Sources */,
@@ -2609,6 +2685,7 @@
B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */,
B5831F452212700500D8604C /* Where.Expression.swift in Sources */,
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
B5ECDBE31CA6BB2B00C7F112 /* CSBaseDataTransaction+Querying.swift in Sources */,
B5ECDC031CA80CBA00C7F112 /* CSWhere.swift in Sources */,
@@ -2650,6 +2727,7 @@
B52DD1BA1BE1F94000949AFE /* MigrationChain.swift in Sources */,
B50392FB1C479640009900CA /* NSManagedObject+Transaction.swift in Sources */,
B52F74321E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
B509D7D623C84E1900F42824 /* Transformable.Required.swift in Sources */,
B5220E211D130816009BC71E /* CSObjectObserver.swift in Sources */,
B5D339EF1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
B52DD19F1BE1F92C00949AFE /* SynchronousDataTransaction.swift in Sources */,
@@ -2657,6 +2735,7 @@
B5220E1A1D130791009BC71E /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
B5215CAC1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
B514EF1123A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */,
B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */,
@@ -2665,6 +2744,7 @@
B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B509D7C123C8480B00F42824 /* Value.Required.swift in Sources */,
B5215CA71FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
B5D33A041E96012400C880DE /* Relationship.swift in Sources */,
B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */,
@@ -2685,6 +2765,7 @@
B52DD1A01BE1F92C00949AFE /* UnsafeDataTransaction.swift in Sources */,
B5ECDC331CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */,
B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */,
B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */,
B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B5277675234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */,
@@ -2698,7 +2779,6 @@
B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */,
B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
B5AA37F4235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
B501322E2346A9B100FC238B /* ListPublisher.swift in Sources */,
@@ -2708,6 +2788,7 @@
B56923EF1EB827F6007C4DC9 /* SchemaMappingProvider.swift in Sources */,
B53FBA081CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */,
B509D7C723C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
B5519A5C1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
B5DBE2D51C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
@@ -2716,9 +2797,9 @@
B5D339EA1E9493A500C880DE /* Entity.swift in Sources */,
B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */,
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
B51FE5AF1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */,
B509D7C223C8480B00F42824 /* Value.Optional.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2764,7 +2845,7 @@
files = (
B56321A91BD65219006C9394 /* Progress+Convenience.swift in Sources */,
B5ECDBFC1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionView-UIKit.swift in Sources */,
B5635D162356C39500B80E6B /* DiffableDataSource.CollectionViewAdapter-UIKit.swift in Sources */,
B5CA2B0A1F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
B5C976E91C6E3A5E00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
B56923F71EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
@@ -2775,6 +2856,7 @@
B56321961BD65216006C9394 /* From.swift in Sources */,
B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5D339DA1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
B509D7D023C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */,
B56923FC1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
B55514EC1EED8BF900BAB888 /* From+Querying.swift in Sources */,
@@ -2817,6 +2899,7 @@
B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B55BB4D923503B9600C33E34 /* EnvironmentValues+DataSources.swift in Sources */,
B5ECDC2C1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
B509D7CB23C8491C00F42824 /* Relationship.ToManyOrdered.swift in Sources */,
18166888232B9ED70097C275 /* String+KeyPaths.swift in Sources */,
B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */,
B56923F21EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
@@ -2835,6 +2918,7 @@
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */,
B5DBE2D41C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
B514EF1323A8DB1D0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
B50392FA1C47963F009900CA /* NSManagedObject+Transaction.swift in Sources */,
B53FBA1B1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
B5519A5B1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
@@ -2876,6 +2960,7 @@
B563219B1BD65216006C9394 /* Tweak.swift in Sources */,
B52F74311E9B50D0005F3DAC /* SchemaHistory.swift in Sources */,
B563218F1BD65216006C9394 /* ImportableObject.swift in Sources */,
B509D7D523C84E1900F42824 /* Transformable.Required.swift in Sources */,
B56321991BD65216006C9394 /* OrderBy.swift in Sources */,
B5D339EE1E9495E500C880DE /* CoreStoreObject+Querying.swift in Sources */,
B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */,
@@ -2883,6 +2968,7 @@
B56321A21BD65216006C9394 /* ListObserver.swift in Sources */,
B50E175923517DE4004F033C /* Differentiable.swift in Sources */,
B5215CAB1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B514EF1023A8DB190093DBA4 /* DiffableDataSource.Target.swift in Sources */,
B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */,
B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
@@ -2891,6 +2977,7 @@
B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */,
B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */,
B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B509D7BF23C8480B00F42824 /* Value.Required.swift in Sources */,
B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5215CA61FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
B5D33A031E96012400C880DE /* Relationship.swift in Sources */,
@@ -2911,6 +2998,7 @@
B563218D1BD65216006C9394 /* CoreStore+Transaction.swift in Sources */,
B546F96B1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B5277674234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */,
B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B563218B1BD65216006C9394 /* UnsafeDataTransaction.swift in Sources */,
B549F6601E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
@@ -2922,7 +3010,7 @@
B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
18166885232B9ED10097C275 /* KeyPath+KeyPaths.swift in Sources */,
B5E8A72221C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
B5AA37F3235C28EE00FFD4B9 /* diffableDataSource.CollectionView-AppKit.swift in Sources */,
B5AA37F3235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
B501322D2346A9B000FC238B /* ListPublisher.swift in Sources */,
@@ -2933,6 +3021,7 @@
B56321861BD65216006C9394 /* CoreStoreLogger.swift in Sources */,
B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */,
B509D7C623C848DA00F42824 /* Relationship.ToOne.swift in Sources */,
B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
@@ -2940,9 +3029,10 @@
B5D339E91E9493A500C880DE /* Entity.swift in Sources */,
B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */,
B56321A41BD65216006C9394 /* CoreStore+Migration.swift in Sources */,
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView-UIKit.swift in Sources */,
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
B56321A01BD65216006C9394 /* ObjectObserver.swift in Sources */,
B56321951BD65216006C9394 /* TypeErasedClauses.swift in Sources */,
B509D7C023C8480B00F42824 /* Value.Optional.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3113,7 +3203,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.1;
MARKETING_VERSION = 7.0.4;
OTHER_LDFLAGS = (
"-weak_framework",
Combine,
@@ -3136,7 +3226,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.1;
MARKETING_VERSION = 7.0.4;
OTHER_LDFLAGS = (
"-weak_framework",
Combine,

View File

@@ -51,7 +51,7 @@ final class CollectionViewDemoViewController: UICollectionViewController {
]
self.filterBarButton = filterBarButton
self.dataSource = DiffableDataSource.CollectionView<Palette>(
self.dataSource = DiffableDataSource.CollectionViewAdapter<Palette>(
collectionView: self.collectionView,
dataStack: ColorsDemo.stack,
cellProvider: { (collectionView, indexPath, palette) in
@@ -118,7 +118,7 @@ final class CollectionViewDemoViewController: UICollectionViewController {
// MARK: Private
private var filterBarButton: UIBarButtonItem?
private var dataSource: DiffableDataSource.CollectionView<Palette>?
private var dataSource: DiffableDataSource.CollectionViewAdapter<Palette>?
deinit {

View File

@@ -16,7 +16,7 @@ final class ListObserverDemoViewController: UITableViewController {
// MARK: - EditableDataSource
final class EditableDataSource: DiffableDataSource.TableView<Palette> {
final class EditableDataSource: DiffableDataSource.TableViewAdapter<Palette> {
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
@@ -130,7 +130,7 @@ final class ListObserverDemoViewController: UITableViewController {
// MARK: Private
private var filterBarButton: UIBarButtonItem?
private var dataSource: DiffableDataSource.TableView<Palette>?
private var dataSource: DiffableDataSource.TableViewAdapter<Palette>?
deinit {

View File

@@ -30,6 +30,10 @@
@import CoreData;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// MARK: - BridgingTests
@implementation BridgingTests
@@ -261,3 +265,5 @@
}
@end
#pragma clang diagnostic pop

View File

@@ -31,6 +31,7 @@ import CoreStore
// MARK: - ErrorTests
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
final class ErrorTests: XCTestCase {
@objc

View File

@@ -45,8 +45,9 @@ let package = Package(
.testTarget(
name: "CoreStoreTests",
dependencies: ["CoreStore"],
path: "CoreStoreTests"
path: "CoreStoreTests",
exclude: ["BridgingTests.h", "BridgingTests.m"]
)
],
swiftLanguageVersions: [.v5_1]
swiftLanguageVersions: [.v5]
)

View File

@@ -1506,9 +1506,9 @@ Note that the owner instance will not be retained. You may call `ListPublisher.r
The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s:
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
```swift
self.dataSource = DiffableDataSource.CollectionView<Person>(
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
@@ -1883,6 +1883,13 @@ git submodule add https://github.com/JohnEstropia/CoreStore.git <destination dir
```
Drag and drop **CoreStore.xcodeproj** to your project.
### Install through Xcode's Swift Package Manager
From the **File** - **Swift Packages** - **Add Package Dependency…** menu, search for
```
CoreStore
```
where `JohnEstropia` is the *Owner*. Then add to your project.
### Objective-C support

View File

@@ -30,18 +30,21 @@ 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 }
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 }
var valueForSnapshot: Any { get }
var valueForSnapshot: Any? { get }
}

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `AsynchronousDataTransaction`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSAsynchronousDataTransaction: CSBaseDataTransaction, CoreStoreObjectiveCType {
@@ -161,6 +162,7 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction, CoreSto
// MARK: - AsynchronousDataTransaction
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension AsynchronousDataTransaction: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -29,6 +29,7 @@ import CoreData
// MARK: - CSDataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension CSDataStack {
/**

View File

@@ -29,6 +29,7 @@ import CoreData
// MARK: - CSDataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@available(macOS 10.12, *)
extension CSDataStack {

View File

@@ -29,6 +29,7 @@ import CoreData
// MARK: - CSDataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension CSDataStack {
/**

View File

@@ -28,6 +28,7 @@ import Foundation
// MARK: - CSDataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension CSDataStack {
/**

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `DataStack`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
@@ -209,6 +210,7 @@ public final class CSDataStack: NSObject, CoreStoreObjectiveCType {
// MARK: - DataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension DataStack: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -35,7 +35,7 @@ import CoreData
- SeeAlso: `CoreStoreError`
*/
@objc
public final class CSError: NSError, CoreStoreObjectiveCType {
public final class CSError: NSError {
/**
The `NSError` error domain for `CSError`.
@@ -44,6 +44,17 @@ public final class CSError: NSError, CoreStoreObjectiveCType {
*/
@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
@@ -67,20 +78,6 @@ public final class CSError: NSError, CoreStoreObjectiveCType {
return "(\(String(reflecting: Self.self))) \(self.bridgeToSwift.coreStoreDumpString)"
}
// MARK: CoreStoreObjectiveCType
public var bridgeToSwift: CoreStoreError {
if let swift = self.swiftError {
return swift
}
let swift = CoreStoreError(_bridgedNSError: self) ?? .unknown
self.swiftError = swift
return swift
}
/**
Do not call directly!
*/
@@ -101,6 +98,9 @@ public final class CSError: NSError, CoreStoreObjectiveCType {
private var swiftError: CoreStoreError?
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension CSError: CoreStoreObjectiveCType {}
// MARK: - CSErrorCode
@@ -110,6 +110,7 @@ public final class CSError: NSError, CoreStoreObjectiveCType {
- SeeAlso: `CSError`
- SeeAlso: `CoreStoreError`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public enum CSErrorCode: Int {
@@ -152,15 +153,7 @@ public enum CSErrorCode: Int {
// MARK: - CoreStoreError
extension CoreStoreError: CoreStoreSwiftType, _ObjectiveCBridgeableError {
// MARK: CoreStoreSwiftType
public var bridgeToObjectiveC: CSError {
return CSError(self)
}
extension CoreStoreError: _ObjectiveCBridgeableError {
// MARK: _ObjectiveCBridgeableError
@@ -260,9 +253,11 @@ extension CoreStoreError: CoreStoreSwiftType, _ObjectiveCBridgeableError {
}
// MARK: Internal
// MARK: - Error
extension Error {
// MARK: Internal
internal var bridgeToSwift: CoreStoreError {
@@ -281,7 +276,8 @@ extension Error {
return .unknown
}
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal var bridgeToObjectiveC: NSError {
switch self {
@@ -297,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)
}
}

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `InMemoryStore`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSInMemoryStore: NSObject, CSStorageInterface, CoreStoreObjectiveCType {
@@ -121,6 +122,7 @@ public final class CSInMemoryStore: NSObject, CSStorageInterface, CoreStoreObjec
// MARK: - InMemoryStore
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension InMemoryStore: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `MigrationResult`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSMigrationResult: NSObject, CoreStoreObjectiveCType {
@@ -173,6 +174,7 @@ public final class CSMigrationResult: NSObject, CoreStoreObjectiveCType {
// MARK: - MigrationResult
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension MigrationResult {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `MigrationType`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSMigrationType: NSObject, CoreStoreObjectiveCType {
@@ -119,6 +120,7 @@ public final class CSMigrationType: NSObject, CoreStoreObjectiveCType {
// MARK: - MigrationType
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension MigrationType: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `SQLiteStore`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCType {
@@ -196,6 +197,7 @@ public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCT
// MARK: - SQLiteStore
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension SQLiteStore: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `SetupResult`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSSetupResult: NSObject {
@@ -177,6 +178,7 @@ public final class CSSetupResult: NSObject {
// MARK: - SetupResult
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension SetupResult where Success: StorageInterface, Success: CoreStoreSwiftType, Success.ObjectiveCType: CSStorageInterface, Failure == CoreStoreError {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `SynchronousDataTransaction`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStoreObjectiveCType {
@@ -148,6 +149,7 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStor
// MARK: - SynchronousDataTransaction
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension SynchronousDataTransaction: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `Tweak`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSTweak: NSObject, CSFetchClause, CSQueryClause, CSDeleteClause, CoreStoreObjectiveCType {
@@ -90,6 +91,7 @@ public final class CSTweak: NSObject, CSFetchClause, CSQueryClause, CSDeleteClau
// MARK: - Tweak
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension Tweak: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import Foundation
- SeeAlso: `UnsafeDataModelSchema`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSUnsafeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
@@ -104,6 +105,7 @@ public final class CSUnsafeDataModelSchema: NSObject, CSDynamicSchema, CoreStore
// MARK: - UnsafeDataModelSchema
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension UnsafeDataModelSchema: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -34,6 +34,7 @@ import CoreData
- SeeAlso: `UnsafeDataTransaction`
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@objc
public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObjectiveCType {
/**
@@ -208,6 +209,7 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
// MARK: - UnsafeDataTransaction
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension UnsafeDataTransaction: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -35,6 +35,7 @@ import Foundation
- SeeAlso: `XcodeDataModelSchema`
*/
@objc
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
public final class CSXcodeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
/**
@@ -104,6 +105,7 @@ public final class CSXcodeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreO
// MARK: - XcodeDataModelSchema
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
extension XcodeDataModelSchema: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType

View File

@@ -373,15 +373,34 @@ fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConve
var coreStoreDumpString: String {
return createFormattedString(
"\"\(self.sectionInfo.name)\" (", ")",
("numberOfObjects", self.sectionInfo.numberOfObjects),
("indexTitle", self.sectionInfo.indexTitle as Any)
"\"\(self.sectionName)\" (", ")",
("numberOfObjects", self.numberOfObjects),
("indexTitle", self.sectionIndexTitle as Any)
)
}
// MARK: FilePrivate
let sectionInfo: NSFetchedResultsSectionInfo
fileprivate init(_ sectionInfo: NSFetchedResultsSectionInfo) {
self.sectionName = sectionInfo.name
self.numberOfObjects = sectionInfo.numberOfObjects
self.sectionIndexTitle = sectionInfo.indexTitle
}
fileprivate init(_ section: Internals.DiffableDataSourceSnapshot.Section) {
self.sectionName = section.differenceIdentifier
self.numberOfObjects = section.elements.count
self.sectionIndexTitle = nil
}
// MARK: Private
private let sectionName: String
private let sectionIndexTitle: String?
private let numberOfObjects: Int
}
@available(macOS 10.12, *)
@@ -409,6 +428,56 @@ extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvert
}
// MARK: - ListPublisher
@available(macOS 10.12, *)
extension ListPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("snapshot", self.snapshot)
)
}
}
// MARK: - ListSnapshot
extension ListSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("numberOfObjects", self.numberOfItems),
("sections", self.diffableSnapshot.sections.map(CoreStoreFetchedSectionInfoWrapper.init))
)
}
}
// MARK: - LocalStorageOptions
extension LocalStorageOptions: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
@@ -568,6 +637,56 @@ extension ObjectMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConve
}
// MARK: - ObjectPublisher
extension ObjectPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("objectID", self.objectID()),
("object", self.object as Any)
)
}
}
// MARK: - ObjectSnapshot
extension ObjectSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("objectID", self.objectID()),
("dictionaryForValues", self.dictionaryForValues())
)
}
}
// MARK: - OrderBy
extension OrderBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {

View File

@@ -37,7 +37,7 @@
#error CoreStore Objective-C utilities can only be used on platforms that support C function overloading
#endif
#define CORESTORE_EXTERN extern
#define CORESTORE_EXTERN extern __deprecated_msg("CoreStore Objective-C API will be removed soon.")
#define CORESTORE_OVERLOADABLE __attribute__((__overloadable__))
#define CORESTORE_REQUIRES_NIL_TERMINATION __attribute__((sentinel(0, 1)))
#define CORESTORE_RETURNS_RETAINED __attribute__((ns_returns_retained))

View File

@@ -31,6 +31,7 @@ import Foundation
/**
`CoreStoreObjectiveCType`s are Objective-C accessible classes that represent CoreStore's Swift types.
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
public protocol CoreStoreObjectiveCType: AnyObject {
/**
@@ -55,6 +56,7 @@ public protocol CoreStoreObjectiveCType: AnyObject {
/**
`CoreStoreSwiftType`s are CoreStore's Swift types that are bridgeable to Objective-C.
*/
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
public protocol CoreStoreSwiftType {
/**
@@ -71,21 +73,25 @@ public protocol CoreStoreSwiftType {
// MARK: - Internal
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T: CoreStoreSwiftType>(_ closure: () -> T) -> T.ObjectiveCType {
return closure().bridgeToObjectiveC
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T: CoreStoreSwiftType>(_ closure: () -> [T]) -> [T.ObjectiveCType] {
return closure().map { $0.bridgeToObjectiveC }
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T: CoreStoreSwiftType>(_ closure: () -> T?) -> T.ObjectiveCType? {
return closure()?.bridgeToObjectiveC
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T: CoreStoreSwiftType>(_ closure: () throws -> T) throws -> T.ObjectiveCType {
do {
@@ -98,6 +104,7 @@ internal func bridge<T: CoreStoreSwiftType>(_ closure: () throws -> T) throws ->
}
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge(_ closure: () throws -> Void) throws {
do {
@@ -110,6 +117,7 @@ internal func bridge(_ closure: () throws -> Void) throws {
}
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T: CoreStoreSwiftType>(_ error: NSErrorPointer, _ closure: () throws -> T) -> T.ObjectiveCType? {
do {
@@ -125,6 +133,7 @@ internal func bridge<T: CoreStoreSwiftType>(_ error: NSErrorPointer, _ closure:
}
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge(_ error: NSErrorPointer, _ closure: () throws -> Void) -> Bool {
do {
@@ -140,6 +149,7 @@ internal func bridge(_ error: NSErrorPointer, _ closure: () throws -> Void) -> B
}
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T>(_ error: NSErrorPointer, _ closure: () throws -> T?) -> T? {
do {
@@ -155,6 +165,7 @@ internal func bridge<T>(_ error: NSErrorPointer, _ closure: () throws -> T?) ->
}
}
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
internal func bridge<T: CoreStoreSwiftType>(_ error: NSErrorPointer, _ closure: () throws -> [T]) -> [T.ObjectiveCType]? {
do {

View File

@@ -292,17 +292,18 @@ public final class CoreStoreSchema: DynamicSchema {
!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 +311,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
@@ -384,12 +386,12 @@ public final class CoreStoreSchema: DynamicSchema {
switch property {
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,

View File

@@ -41,7 +41,19 @@ extension DataStack {
*/
public func publishObject<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
return ObjectPublisher<O>(objectID: object.cs_id(), context: self.unsafeContext())
return self.publishObject(object.cs_id())
}
/**
Creates an `ObjectPublisher` for a `DynamicObject` with the specified `ObjectID`. Multiple objects may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter objectID: the `ObjectID` of the object to observe changes from
- returns: an `ObjectPublisher` that broadcasts changes to `object`
*/
public func publishObject<O: DynamicObject>(_ objectID: O.ObjectID) -> ObjectPublisher<O> {
let context = self.unsafeContext()
return context.objectPublisher(objectID: objectID)
}
/**

View File

@@ -0,0 +1,220 @@
//
// DiffableDataSource.BaseAdapter.swift
// CoreStore
//
// Copyright © 2018 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)
import Foundation
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - BaseAdapter
/**
The `DiffableDataSource.BaseAdapter` serves as a superclass for consumers of `ListPublisher` and `ListSnapshot` diffable data.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class BaseAdapter<O: DynamicObject, T: Target>: NSObject {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
The target to be updated by this dataSource
*/
public let target: T
/**
The `DataStack` where object fetches are performed
*/
public let dataStack: DataStack
/**
Initializes the `DiffableDataSource.BaseAdapter` object. This instance needs to be held on (retained) for as long as the target's lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapterAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(target: T, dataStack: DataStack) {
self.target = target
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
}
/**
Clears the target.
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
*/
open func purge(animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
self.dispatcher.purge(
target: self.target,
animatingDifferences: animatingDifferences,
performUpdates: { target, changeset, setSections in
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
)
},
completion: completion
)
}
/**
Reloads the target using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
``
- parameter snapshot: the `ListSnapshot` used to reload the target with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
*/
open func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot,
target: self.target,
animatingDifferences: animatingDifferences,
performUpdates: { target, changeset, setSections in
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
)
},
completion: completion
)
}
/**
Returns the number of sections
- parameter indexPath: the `IndexPath` to search for
- returns: the number of sections
*/
public func numberOfSections() -> Int {
return self.dispatcher.numberOfSections()
}
/**
Returns the number of items at the specified section, or `nil` if the section is not found
- parameter section: the section index to search for
- returns: the number of items at the specified section, or `nil` if the section is not found
*/
public func numberOfItems(inSection section: Int) -> Int? {
return self.dispatcher.numberOfItems(inSection: section)
}
/**
Returns the section identifier at the specified index, or `nil` if not found
- parameter section: the section index to search for
- returns: the section identifier at the specified indec, or `nil` if not found
*/
public func sectionID(for section: Int) -> String? {
return self.dispatcher.sectionIdentifier(inSection: section)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: Internal
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}
#endif

View File

@@ -1,297 +0,0 @@
//
// DiffableDataSource.CollectionView-UIKit.swift
// CoreStore
//
// Copyright © 2018 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) && (os(iOS) || os(tvOS))
import UIKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionView` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `UICollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.CollectionView` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionView<O: DynamicObject>: NSObject, UICollectionViewDataSource {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
*/
@nonobjc
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
self.collectionView = collectionView
self.cellProvider = cellProvider
self.supplementaryViewProvider = supplementaryViewProvider
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
super.init()
collectionView.dataSource = self
}
/**
Reloads the `UICollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
view: self.collectionView,
animatingDifferences: animatingDifferences,
performUpdates: { collectionView, changeset, setSections in
collectionView.reload(
using: changeset,
setData: setSections
)
}
)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
@nonobjc
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
@nonobjc
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: - UICollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.dispatcher.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dispatcher.numberOfItems(inSection: section)
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let cell = self.cellProvider(collectionView, indexPath, object) else {
Internals.abort("\(Internals.typeName(UICollectionViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return cell
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
return UICollectionReusableView()
}
return view
}
// MARK: Private
private weak var collectionView: UICollectionView?
private let dataStack: DataStack
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}
// MARK: - UICollectionView
extension UICollectionView {
// MARK: FilePrivate
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if case .none = window, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.performBatchUpdates(
{
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(IndexSet(changeset.sectionDeleted))
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(IndexSet(changeset.sectionInserted))
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(IndexSet(changeset.sectionUpdated))
}
for (source, target) in changeset.sectionMoved {
self.moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
self.deleteItems(
at: changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) }
)
}
if !changeset.elementInserted.isEmpty {
self.insertItems(
at: changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) }
)
}
if !changeset.elementUpdated.isEmpty {
self.reloadItems(
at: changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) }
)
}
for (source, target) in changeset.elementMoved {
self.moveItem(
at: IndexPath(item: source.element, section: source.section),
to: IndexPath(item: target.element, section: target.section)
)
}
},
completion: nil
)
}
}
}
#endif

View File

@@ -0,0 +1,232 @@
//
// DiffableDataSource.CollectionViewAdapter-AppKit.swift
// CoreStore
//
// Copyright © 2018 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(AppKit) && os(macOS)
import AppKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionViewAdapter` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `NSCollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
item.setPerson(person)
return item
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<NSCollectionView>>, NSCollectionViewDataSource {
// MARK: Public
/**
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
item.setPerson(person)
return item
}
)
```
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
*/
@nonobjc
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
self.itemProvider = itemProvider
self.supplementaryViewProvider = supplementaryViewProvider
super.init(target: .init(collectionView), dataStack: dataStack)
collectionView.dataSource = self
}
// MARK: - NSCollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let item = self.itemProvider(collectionView, indexPath, object) else {
Internals.abort("\(Internals.typeName(NSCollectionViewDataSource.self)) returned a `nil` item for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return item
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
return NSView()
}
return view
}
// MARK: Private
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
}
// MARK: - DefaultCollectionViewTarget
public struct DefaultCollectionViewTarget<T: NSCollectionView>: Target {
// MARK: Public
public typealias Base = T
public private(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target:
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteItems(at: Set(indexPaths))
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertItems(at: Set(indexPaths))
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadItems(at: Set(indexPaths))
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "CollectionViewAdapter")
public typealias CollectionView = CollectionViewAdapter
}
#endif

View File

@@ -0,0 +1,231 @@
//
// DiffableDataSource.CollectionViewAdapter-UIKit.swift
// CoreStore
//
// Copyright © 2018 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) && (os(iOS) || os(tvOS))
import UIKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionViewAdapter` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `UICollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<UICollectionView>>, UICollectionViewDataSource {
// MARK: Public
/**
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
*/
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
self.cellProvider = cellProvider
self.supplementaryViewProvider = supplementaryViewProvider
super.init(target: .init(collectionView), dataStack: dataStack)
collectionView.dataSource = self
}
// MARK: - UICollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let cell = self.cellProvider(collectionView, indexPath, object) else {
Internals.abort("\(Internals.typeName(UICollectionViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return cell
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
return UICollectionReusableView()
}
return view
}
// MARK: Private
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
}
// MARK: - DefaultCollectionViewTarget
public struct DefaultCollectionViewTarget<T: UICollectionView>: Target {
// MARK: Public
public typealias Base = T
public private(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteItems(at: indexPaths)
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertItems(at: indexPaths)
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadItems(at: indexPaths)
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
self.base?.performBatchUpdates(updates, completion: nil)
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "CollectionViewAdapter")
public typealias CollectionView = CollectionViewAdapter
}
#endif

View File

@@ -1,372 +0,0 @@
//
// DiffableDataSource.TableView-UIKit.swift
// CoreStore
//
// Copyright © 2018 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) && (os(iOS) || os(tvOS))
import UIKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - TableView
/**
The `DiffableDataSource.TableView` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableView` may override some `UITableViewDataSource` methods as needed.
The `DiffableDataSource.TableView` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableView<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.TableView` fully handles the reload animations. To turn change the default animation, set the `defaultRowAnimation`.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class TableView<O: DynamicObject>: NSObject, UITableViewDataSource {
// MARK: Open
/**
The animation style for row changes
*/
@nonobjc
open var defaultRowAnimation: UITableView.RowAnimation = .automatic
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
Initializes the `DiffableDataSource.TableView`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableView<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableView`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
@nonobjc
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
self.tableView = tableView
self.cellProvider = cellProvider
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
super.init()
tableView.dataSource = self
}
/**
Reloads the `UITableView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
If the `defaultRowAnimation` is configured to, animations are also applied accordingly.
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
@nonobjc
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
let diffableSnapshot = snapshot.diffableSnapshot
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// self.modernDataSource.apply(
// diffableSnapshot as! NSDiffableDataSourceSnapshot<String, NSManagedObjectID>,
// animatingDifferences: animatingDifferences,
// completion: nil
// )
// }
// else {
self.dispatcher.apply(
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
view: self.tableView,
animatingDifferences: animatingDifferences,
performUpdates: { tableView, changeset, setSections in
tableView.reload(
using: changeset,
with: self.defaultRowAnimation,
setData: setSections
)
}
)
// }
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
@nonobjc
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
@nonobjc
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: - UITableViewDataSource
@objc
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
return self.dispatcher.numberOfSections()
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dispatcher.numberOfItems(inSection: section)
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.dispatcher.sectionIdentifier(inSection: section)
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return nil
}
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let cell = self.cellProvider(tableView, indexPath, object) else {
Internals.abort("\(Internals.typeName(UITableViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return cell
}
@objc
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
@objc
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
@objc
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
// MARK: Private
@nonobjc
private weak var tableView: UITableView?
@nonobjc
private let dataStack: DataStack
@nonobjc
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
@nonobjc
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}
// MARK: - UITableView
extension UITableView {
// MARK: FilePrivate
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
with animation: @autoclosure () -> RowAnimation,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
self.reload(
using: stagedChangeset,
deleteSectionsAnimation: animation(),
insertSectionsAnimation: animation(),
reloadSectionsAnimation: animation(),
deleteRowsAnimation: animation(),
insertRowsAnimation: animation(),
reloadRowsAnimation: animation(),
interrupt: interrupt,
setData: setData
)
}
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
deleteSectionsAnimation: @autoclosure () -> RowAnimation,
insertSectionsAnimation: @autoclosure () -> RowAnimation,
reloadSectionsAnimation: @autoclosure () -> RowAnimation,
deleteRowsAnimation: @autoclosure () -> RowAnimation,
insertRowsAnimation: @autoclosure () -> RowAnimation,
reloadRowsAnimation: @autoclosure () -> RowAnimation,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if case .none = window, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.cs_performBatchUpdates {
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(IndexSet(changeset.sectionDeleted), with: deleteSectionsAnimation())
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(IndexSet(changeset.sectionInserted), with: insertSectionsAnimation())
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(IndexSet(changeset.sectionUpdated), with: reloadSectionsAnimation())
}
for (source, target) in changeset.sectionMoved {
self.moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
self.deleteRows(at: changeset.elementDeleted.map { IndexPath(row: $0.element, section: $0.section) }, with: deleteRowsAnimation())
}
if !changeset.elementInserted.isEmpty {
self.insertRows(at: changeset.elementInserted.map { IndexPath(row: $0.element, section: $0.section) }, with: insertRowsAnimation())
}
if !changeset.elementUpdated.isEmpty {
self.reloadRows(at: changeset.elementUpdated.map { IndexPath(row: $0.element, section: $0.section) }, with: reloadRowsAnimation())
}
for (source, target) in changeset.elementMoved {
self.moveRow(at: IndexPath(row: source.element, section: source.section), to: IndexPath(row: target.element, section: target.section))
}
}
}
}
@nonobjc
private func cs_performBatchUpdates(_ updates: () -> Void) {
if #available(iOS 11.0, tvOS 11.0, *) {
self.performBatchUpdates(updates)
}
else {
self.beginUpdates()
updates()
self.endUpdates()
}
}
}
#endif

View File

@@ -0,0 +1,266 @@
//
// DiffableDataSource.TableViewAdapter-UIKit.swift
// CoreStore
//
// Copyright © 2018 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) && (os(iOS) || os(tvOS))
import UIKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - TableViewAdapter
/**
The `DiffableDataSource.TableViewAdapterAdapter` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableViewAdapter` may override some `UITableViewDataSource` methods as needed.
The `DiffableDataSource.TableViewAdapterAdapter` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.TableViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class TableViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultTableViewTarget<UITableView>>, UITableViewDataSource {
// MARK: Publi
/**
Initializes the `DiffableDataSource.TableViewAdapter`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
self.cellProvider = cellProvider
super.init(target: .init(tableView), dataStack: dataStack)
tableView.dataSource = self
}
/**
The target `UITableView`
*/
public var tableView: UITableView? {
return self.target.base
}
// MARK: - UITableViewDataSource
@objc
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionID(for: section)
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return nil
}
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let cell = self.cellProvider(tableView, indexPath, object) else {
Internals.abort("\(Internals.typeName(UITableViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return cell
}
@objc
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
@objc
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
@objc
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
// MARK: Private
@nonobjc
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
}
// MARK: - DefaultTableViewTarget
public struct DefaultTableViewTarget<T: UITableView>: Target {
// MARK: Public
public typealias Base = T
public private(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices, with: .automatic)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices, with: .automatic)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices, with: .automatic)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteRows(at: indexPaths, with: .automatic)
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertRows(at: indexPaths, with: .automatic)
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadRows(at: indexPaths, with: .automatic)
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveRow(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
guard let base = self.base else {
return
}
if #available(iOS 11.0, tvOS 11.0, *) {
base.performBatchUpdates(updates)
}
else {
base.beginUpdates()
updates()
base.endUpdates()
}
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "TableViewAdapter")
public typealias TableView = TableViewAdapter
}
#endif

View File

@@ -0,0 +1,215 @@
//
// DiffableDataSource.Target.swift
// CoreStore
//
// Copyright © 2018 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)
import Foundation
// MARK: - DiffableDataSourc
extension DiffableDataSource {
// MARK: - Target
/**
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
*/
public typealias Target = DiffableDataSourceTarget
}
// MARK: - DiffableDataSource.Target
/**
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
*/
public protocol DiffableDataSourceTarget {
// MARK: Public
/**
Whether `reloadData()` should be executed instead of `performBatchUpdates(updates:animated:)`.
*/
var shouldSuspendBatchUpdates: Bool { get }
/**
Deletes one or more sections.
*/
func deleteSections(at indices: IndexSet, animated: Bool)
/**
Inserts one or more sections
*/
func insertSections(at indices: IndexSet, animated: Bool)
/**
Reloads the specified sections.
*/
func reloadSections(at indices: IndexSet, animated: Bool)
/**
Moves a section to a new location.
*/
func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool)
/**
Deletes the items specified by an array of index paths.
*/
func deleteItems(at indexPaths: [IndexPath], animated: Bool)
/**
Inserts items at the locations identified by an array of index paths.
*/
func insertItems(at indexPaths: [IndexPath], animated: Bool)
/**
Reloads the specified items.
*/
func reloadItems(at indexPaths: [IndexPath], animated: Bool)
/**
Moves the item at a specified location to a destination location.
*/
func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool)
/**
Animates multiple insert, delete, reload, and move operations as a group.
*/
func performBatchUpdates(updates: () -> Void, animated: Bool)
/**
Reloads all sections and items.
*/
func reloadData()
}
extension DiffableDataSource.Target {
// MARK: Internal
internal func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
animated: Bool,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
for changeset in stagedChangeset {
if let interrupt = interrupt,
interrupt(changeset),
let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.performBatchUpdates(
updates: {
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(
at: IndexSet(changeset.sectionDeleted),
animated: animated
)
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(
at: IndexSet(changeset.sectionInserted),
animated: animated
)
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(
at: IndexSet(changeset.sectionUpdated),
animated: animated
)
}
for (source, target) in changeset.sectionMoved {
self.moveSection(
at: source,
to: target,
animated: animated
)
}
if !changeset.elementDeleted.isEmpty {
self.deleteItems(
at: changeset.elementDeleted.map {
IndexPath(item: $0.element, section: $0.section)
},
animated: animated
)
}
if !changeset.elementInserted.isEmpty {
self.insertItems(
at: changeset.elementInserted.map {
IndexPath(item: $0.element, section: $0.section)
},
animated: animated
)
}
if !changeset.elementUpdated.isEmpty {
self.reloadItems(
at: changeset.elementUpdated.map {
IndexPath(item: $0.element, section: $0.section)
},
animated: animated
)
}
for (source, target) in changeset.elementMoved {
self.moveItem(
at: IndexPath(item: source.element, section: source.section),
to: IndexPath(item: target.element, section: target.section),
animated: animated
)
}
},
animated: animated
)
}
}
}
#endif

View File

@@ -23,12 +23,12 @@
// SOFTWARE.
//
#if os(iOS) || os(tvOS) || os(macOS)
#if canImport(UIKit) || canImport(AppKit)
// MARK: - DiffableDataSource
/**
Namespace for diffable data source types. See `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView` for actual implementations
Namespace for diffable data source types. See `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` for actual implementations
*/
public enum DiffableDataSource {}

View File

@@ -47,9 +47,13 @@ extension Internals {
// MARK: Internal
init(sections: [NSFetchedResultsSectionInfo]) {
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int) {
self.structure = .init(sections: sections)
self.structure = .init(
sections: sections,
fetchOffset: Swift.max(0, fetchOffset),
fetchLimit: (fetchLimit > 0) ? fetchLimit : nil
)
}
var sections: [Section] {
@@ -92,6 +96,11 @@ extension Internals {
return self.structure.allItemIDs
}
var updatedItemIdentifiers: Set<NSManagedObjectID> {
return self.structure.reloadedItems
}
func numberOfItems(inSection identifier: String) -> Int {
return self.itemIdentifiers(inSection: identifier).count
@@ -117,22 +126,22 @@ extension Internals {
return self.structure.allSectionIDs.firstIndex(of: identifier)
}
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?) {
mutating func appendItems<C: Collection>(_ identifiers: C, toSection sectionIdentifier: String?) where C.Element == NSManagedObjectID {
self.structure.append(itemIDs: identifiers, to: sectionIdentifier)
}
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID) {
mutating func insertItems<C: Collection>(_ identifiers: C, beforeItem beforeIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID {
self.structure.insert(itemIDs: identifiers, before: beforeIdentifier)
}
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID) {
mutating func insertItems<C: Collection>(_ identifiers: C, afterItem afterIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID {
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
}
mutating func deleteItems(_ identifiers: [NSManagedObjectID]) {
mutating func deleteItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
self.structure.remove(itemIDs: identifiers)
}
@@ -152,27 +161,27 @@ extension Internals {
self.structure.move(itemID: identifier, after: toIdentifier)
}
mutating func reloadItems(_ identifiers: [NSManagedObjectID]) {
mutating func reloadItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
self.structure.update(itemIDs: identifiers)
}
mutating func appendSections(_ identifiers: [String]) {
mutating func appendSections<C: Collection>(_ identifiers: C) where C.Element == String {
self.structure.append(sectionIDs: identifiers)
}
mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String) {
mutating func insertSections<C: Collection>(_ identifiers: C, beforeSection toIdentifier: String) where C.Element == String {
self.structure.insert(sectionIDs: identifiers, before: toIdentifier)
}
mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String) {
mutating func insertSections<C: Collection>(_ identifiers: C, afterSection toIdentifier: String) where C.Element == String {
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
}
mutating func deleteSections(_ identifiers: [String]) {
mutating func deleteSections<S: Sequence>(_ identifiers: S) where S.Element == String {
self.structure.remove(sectionIDs: identifiers)
}
@@ -187,7 +196,7 @@ extension Internals {
self.structure.move(sectionID: identifier, after: toIdentifier)
}
mutating func reloadSections(_ identifiers: [String]) {
mutating func reloadSections<S: Sequence>(_ identifiers: S) where S.Element == String {
self.structure.update(sectionIDs: identifiers)
}
@@ -268,23 +277,54 @@ extension Internals {
// MARK: Internal
var sections: [Section]
private(set) var reloadedItems: Set<NSManagedObjectID>
init() {
self.sections = []
self.reloadedItems = []
}
init(sections: [NSFetchedResultsSectionInfo]) {
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int?) {
self.sections = sections.map {
let sliceItems: (_ array: [Any], _ offset: Int) -> Array<Any>.SubSequence
if let fetchLimit = fetchLimit {
Section(
differenceIdentifier: $0.name,
items: $0.objects?
.compactMap({ ($0 as? NSManagedObject)?.objectID })
.map({ Item(differenceIdentifier: $0) }) ?? []
var remainingCount = fetchLimit
sliceItems = {
let slice = $0[$1...].prefix(remainingCount)
remainingCount -= slice.count
return slice
}
}
else {
sliceItems = { $0[$1...] }
}
var newSections: [Internals.DiffableDataSourceSnapshot.Section] = []
var ignoreCount = fetchOffset
for section in sections {
let objects = section.objects ?? []
guard objects.indices.contains(ignoreCount) else {
ignoreCount -= objects.count
continue
}
let items = sliceItems(objects, ignoreCount)
.map({ Item(differenceIdentifier: ($0 as! NSManagedObject).objectID) })
ignoreCount = 0
guard !items.isEmpty else {
continue
}
newSections.append(
Section(differenceIdentifier: section.name, items: items)
)
}
self.sections = newSections
self.reloadedItems = []
}
var allSectionIDs: [String] {
@@ -308,10 +348,10 @@ extension Internals {
func section(containing itemID: NSManagedObjectID) -> String? {
return self.itemPositionMap()[itemID]?.section.differenceIdentifier
return self.itemPositionMap(itemID)?.section.differenceIdentifier
}
mutating func append(itemIDs: [NSManagedObjectID], to sectionID: String?) {
mutating func append<C: Collection>(itemIDs: C, to sectionID: String?) where C.Element == NSManagedObjectID {
let index: Array<Section>.Index
if let sectionID = sectionID {
@@ -335,9 +375,9 @@ extension Internals {
self.sections[index].elements.append(contentsOf: items)
}
mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID) {
mutating func insert<C: Collection>(itemIDs: C, before beforeItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
Internals.abort("Item \(beforeItemID) does not exist")
}
@@ -346,9 +386,9 @@ extension Internals {
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
}
mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: NSManagedObjectID) {
mutating func insert<C: Collection>(itemIDs: C, after afterItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
guard let itemPosition = self.itemPositionMap(afterItemID) else {
Internals.abort("Item \(afterItemID) does not exist")
}
@@ -359,7 +399,7 @@ extension Internals {
.insert(contentsOf: items, at: itemIndex)
}
mutating func remove(itemIDs: [NSManagedObjectID]) {
mutating func remove<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
let itemPositionMap = self.itemPositionMap()
var removeIndexSetMap: [Int: IndexSet] = [:]
@@ -390,13 +430,18 @@ extension Internals {
}
}
mutating func removeAllEmptySections() {
self.sections.removeAll(where: { $0.elements.isEmpty })
}
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
guard let removed = self.remove(itemID: itemID) else {
Internals.abort("Item \(itemID) does not exist")
}
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
Internals.abort("Item \(beforeItemID) does not exist")
}
@@ -410,7 +455,7 @@ extension Internals {
Internals.abort("Item \(itemID) does not exist")
}
guard let itemPosition = self.itemPositionMap()[afterItemID] else {
guard let itemPosition = self.itemPositionMap(afterItemID) else {
Internals.abort("Item \(afterItemID) does not exist")
}
@@ -423,6 +468,7 @@ extension Internals {
mutating func update<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
let itemPositionMap = self.itemPositionMap()
var newItemIDs: Set<NSManagedObjectID> = []
for itemID in itemIDs {
guard let itemPosition = itemPositionMap[itemID] else {
@@ -431,16 +477,18 @@ extension Internals {
}
self.sections[itemPosition.sectionIndex]
.elements[itemPosition.itemRelativeIndex].isReloaded = true
newItemIDs.insert(itemID)
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func append(sectionIDs: [String]) {
mutating func append<C: Collection>(sectionIDs: C) where C.Element == String {
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.append(contentsOf: newSections)
}
mutating func insert(sectionIDs: [String], before beforeSectionID: String) {
mutating func insert<C: Collection>(sectionIDs: C, before beforeSectionID: String) where C.Element == String {
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
@@ -450,7 +498,7 @@ extension Internals {
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func insert(sectionIDs: [String], after afterSectionID: String) {
mutating func insert<C: Collection>(sectionIDs: C, after afterSectionID: String) where C.Element == String {
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
@@ -461,7 +509,7 @@ extension Internals {
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func remove(sectionIDs: [String]) {
mutating func remove<S: Sequence>(sectionIDs: S) where S.Element == String {
for sectionID in sectionIDs {
@@ -519,7 +567,7 @@ extension Internals {
@discardableResult
private mutating func remove(itemID: NSManagedObjectID) -> Item? {
guard let itemPosition = self.itemPositionMap()[itemID] else {
guard let itemPosition = self.itemPositionMap(itemID) else {
return nil
}
@@ -537,6 +585,28 @@ extension Internals {
return self.sections.remove(at: sectionIndex)
}
private func itemPositionMap(_ itemID: NSManagedObjectID) -> ItemPosition? {
let sections = self.sections
for (sectionIndex, section) in sections.enumerated() {
for (itemRelativeIndex, item) in section.elements.enumerated() {
guard item.differenceIdentifier == itemID else {
continue
}
return ItemPosition(
item: item,
itemRelativeIndex: itemRelativeIndex,
section: section,
sectionIndex: sectionIndex
)
}
}
return nil
}
private func itemPositionMap() -> [NSManagedObjectID: ItemPosition] {
return self.sections.enumerated().reduce(into: [:]) { result, section in

View File

@@ -51,8 +51,38 @@ extension Internals {
self.dataStack = dataStack
}
func purge<Target: DiffableDataSource.Target>(
target: Target?,
animatingDifferences: Bool,
performUpdates: @escaping (
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) {
self.apply(
.init(),
target: target,
animatingDifferences: animatingDifferences,
performUpdates: performUpdates,
completion: completion
)
}
func apply<View: AnyObject>(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) {
func apply<Target: DiffableDataSource.Target>(
_ snapshot: DiffableDataSourceSnapshot,
target: Target?,
animatingDifferences: Bool,
performUpdates: @escaping (
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) {
self.dispatcher.dispatch { [weak self] in
@@ -64,36 +94,42 @@ extension Internals {
self.currentSnapshot = snapshot
let newSections = snapshot.sections
guard let view = view else {
guard let target = target else {
return self.sections = newSections
self.sections = newSections
return
}
let performDiffingUpdates: () -> Void = {
let changeset = StagedChangeset(source: self.sections, target: newSections)
performUpdates(view, changeset) { sections in
performUpdates(target, changeset) { sections in
self.sections = sections
}
}
#if canImport(QuartzCore)
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
if !animatingDifferences {
CATransaction.begin()
CATransaction.setDisableActions(true)
performDiffingUpdates()
CATransaction.commit()
return
}
#endif
performDiffingUpdates()
CATransaction.commit()
#else
performDiffingUpdates()
completion()
#endif
}
}
@@ -104,14 +140,23 @@ extension Internals {
return snapshot
}
func sectionIdentifier(inSection section: Int) -> String? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].differenceIdentifier
}
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
guard self.sections.indices.contains(indexPath.section) else {
return nil
}
let items = self.sections[indexPath.section].elements
guard (0 ..< items.endIndex) ~= indexPath.item else {
guard items.indices.contains(indexPath.item) else {
return nil
}
@@ -138,15 +183,14 @@ extension Internals {
return self.sections.count
}
func numberOfItems(inSection section: Int) -> Int {
func numberOfItems(inSection section: Int) -> Int? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].elements.count
}
func sectionIdentifier(inSection section: Int) -> String {
return self.sections[section].differenceIdentifier
}
// MARK: Private

View File

@@ -41,9 +41,6 @@ import AppKit
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot<String, NSManagedObjectID>)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String?
@@ -80,15 +77,6 @@ extension Internals {
internal func initialFetch() {
// #if canImport(UIKit) || canImport(AppKit)
//
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// return
// }
//
// #endif
guard let fetchedResultsController = self.fetchedResultsController else {
return
@@ -98,26 +86,14 @@ extension Internals {
// MARK: NSFetchedResultsControllerDelegate
// #if canImport(UIKit) || canImport(AppKit)
//
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// @objc
// dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
//
// self.handler?.controller(
// controller,
// didChangeContentWith: snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
// )
// }
//
// #endif
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
var snapshot = Internals.DiffableDataSourceSnapshot(
sections: controller.sections ?? []
sections: controller.sections ?? [],
fetchOffset: controller.fetchRequest.fetchOffset,
fetchLimit: controller.fetchRequest.fetchLimit
)
snapshot.reloadSections(self.reloadedSectionIDs)
snapshot.reloadItems(self.reloadedItemIDs)

View File

@@ -62,7 +62,7 @@ import SwiftUI
.orderBy(.ascending(\.lastName))
)
```
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView`.
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
*/
@available(macOS 10.12, *)
public final class ListPublisher<O: DynamicObject>: Hashable {
@@ -397,15 +397,6 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
extension ListPublisher: FetchedDiffableDataSourceSnapshotHandler {
// MARK: FetchedDiffableDataSourceSnapshotHandler
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot<String, NSManagedObjectID>) {
//
// self.snapshot = .init(
// diffableSnapshot: snapshot,
// context: controller.managedObjectContext
// )
// }
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot) {

View File

@@ -37,7 +37,7 @@ import AppKit
// MARK: - ListSnapshot
/**
A `ListSnapshot` holds a stable list of `DynamicObject` identifiers. This is typically created by a `ListPublisher` and are designed to work well with `DiffableDataSource.TableView`s and `DiffableDataSource.CollectionView`s. For detailed examples, see the documentation on `DiffableDataSource.TableView` and `DiffableDataSource.CollectionView`.
A `ListSnapshot` holds a stable list of `DynamicObject` identifiers. This is typically created by a `ListPublisher` and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, see the documentation on `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
While the `ListSnapshot` stores only object identifiers, all accessors to its items return `ObjectPublisher`s, which are lazily created. For more details, see the documentation on `ListObject`.
@@ -83,7 +83,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
*/
public subscript(safeIndex index: Index) -> ObjectPublisher<O>? {
let context = self.context!
guard let context = self.context else {
return nil
}
let itemIDs = self.diffableSnapshot.itemIdentifiers
guard itemIDs.indices.contains(index) else {
@@ -118,7 +121,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
*/
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectPublisher<O>? {
let context = self.context!
guard let context = self.context else {
return nil
}
let snapshot = self.diffableSnapshot
let sectionIDs = snapshot.sectionIdentifiers
guard sectionIDs.indices.contains(sectionIndex) else {
@@ -214,6 +220,14 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
return snapshot.numberOfItems(inSection: sectionID) > 0
}
/**
Returns item identifiers for updated objects. This is mainly useful for Data Source adapters such as `UICollectionViewDiffableDataSource` or `UITableViewDiffableDataSource` which work on collection diffs when reloading. Since objects with same IDs resolve as "equal" in their old and new states, adapters may need extra heuristics to determine which row items need reloading. If your row items are all observing changes from each corresponding `ObjectPublisher`, or if you are using CoreStore's built-in `DiffableDataSource`s, there is no need to inspect this property.
*/
public var updatedItemIdentifiers: Set<NSManagedObjectID> {
return self.diffableSnapshot.updatedItemIdentifiers
}
/**
The number of items in all sections in the `ListSnapshot`
*/
@@ -340,7 +354,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
return indices.map { position in
let itemID = itemIDs[position]
return ObjectPublisher<O>(objectID: itemID, context: context)
return context.objectPublisher(objectID: itemID)
}
}
@@ -354,10 +368,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
return itemIDs.map {
return ObjectPublisher<O>(objectID: $0, context: context)
}
return itemIDs.map(context.objectPublisher(objectID:))
}
/**
@@ -374,7 +385,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
return itemIndices.map { position in
let itemID = itemIDs[position]
return ObjectPublisher<O>(objectID: itemID, context: context)
return context.objectPublisher(objectID: itemID)
}
}
@@ -391,7 +402,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
return indices.lazy.map { position in
let itemID = itemIDs[position]
return ObjectPublisher<O>(objectID: itemID, context: context)
return context.objectPublisher(objectID: itemID)
}
}
@@ -405,10 +416,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
return itemIDs.lazy.map {
return ObjectPublisher<O>(objectID: $0, context: context)
}
return itemIDs.lazy.map(context.objectPublisher(objectID:))
}
/**
@@ -425,7 +433,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
return itemIndices.lazy.map { position in
let itemID = itemIDs[position]
return ObjectPublisher<O>(objectID: itemID, context: context)
return context.objectPublisher(objectID: itemID)
}
}
@@ -438,7 +446,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to append
- parameter sectionID: the section to append the items to
*/
public mutating func appendItems(withIDs itemIDs: [ItemID], toSectionWithID sectionID: SectionID? = nil) {
public mutating func appendItems<C: Collection>(withIDs itemIDs: C, toSectionWithID sectionID: SectionID? = nil) where C.Element == ItemID {
self.diffableSnapshot.appendItems(itemIDs, toSection: sectionID)
}
@@ -449,7 +457,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to insert
- parameter beforeItemID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems(withIDs itemIDs: [ItemID], beforeItemID: ItemID) {
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, beforeItemID: ItemID) where C.Element == ItemID {
self.diffableSnapshot.insertItems(itemIDs, beforeItem: beforeItemID)
}
@@ -460,7 +468,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to insert
- parameter beforeItemID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems(withIDs itemIDs: [ItemID], afterItemID: ItemID) {
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, afterItemID: ItemID) where C.Element == ItemID {
self.diffableSnapshot.insertItems(itemIDs, afterItem: afterItemID)
}
@@ -470,7 +478,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to delete
*/
public mutating func deleteItems(withIDs itemIDs: [ItemID]) {
public mutating func deleteItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
self.diffableSnapshot.deleteItems(itemIDs)
}
@@ -510,7 +518,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers to reload
*/
public mutating func reloadItems(withIDs itemIDs: [ItemID]) {
public mutating func reloadItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
self.diffableSnapshot.reloadItems(itemIDs)
}
@@ -520,7 +528,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the sections to append
*/
public mutating func appendSections(withIDs sectionIDs: [SectionID]) {
public mutating func appendSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.appendSections(sectionIDs)
}
@@ -531,7 +539,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter beforeSectionID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections(withIDs sectionIDs: [SectionID], beforeSectionID: SectionID) {
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, beforeSectionID: SectionID) where C.Element == SectionID {
self.diffableSnapshot.insertSections(sectionIDs, beforeSection: beforeSectionID)
}
@@ -542,7 +550,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter beforeSectionID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections(withIDs sectionIDs: [SectionID], afterSectionID: SectionID) {
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, afterSectionID: SectionID) where C.Element == SectionID {
self.diffableSnapshot.insertSections(sectionIDs, afterSection: afterSectionID)
}
@@ -552,7 +560,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to delete
*/
public mutating func deleteSections(withIDs sectionIDs: [SectionID]) {
public mutating func deleteSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
self.diffableSnapshot.deleteSections(sectionIDs)
}
@@ -584,7 +592,7 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers to reload
*/
public mutating func reloadSections(withIDs sectionIDs: [SectionID]) {
public mutating func reloadSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
self.diffableSnapshot.reloadSections(sectionIDs)
}
@@ -629,28 +637,14 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
// MARK: Internal
internal private(set) var diffableSnapshot: DiffableDataSourceSnapshotProtocol
internal private(set) var diffableSnapshot: Internals.DiffableDataSourceSnapshot
internal init() {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// self.diffableSnapshot = NSDiffableDataSourceSnapshot<String, NSManagedObjectID>()
// }
// else {
self.diffableSnapshot = Internals.DiffableDataSourceSnapshot()
// }
self.diffableSnapshot = Internals.DiffableDataSourceSnapshot()
self.context = nil
}
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// internal init(diffableSnapshot: NSDiffableDataSourceSnapshot<String, NSManagedObjectID>, context: NSManagedObjectContext) {
//
// self.diffableSnapshot = diffableSnapshot
// self.context = context
// }
internal init(diffableSnapshot: Internals.DiffableDataSourceSnapshot, context: NSManagedObjectContext) {
self.diffableSnapshot = diffableSnapshot

View File

@@ -99,7 +99,7 @@ extension NSManagedObjectContext {
return objectPublisher
}
let objectPublisher = ObjectPublisher<O>(objectID: objectID, context: self)
let objectPublisher = ObjectPublisher<O>.createUncached(objectID: objectID, context: self)
cache.setObject(objectPublisher, forKey: objectID)
return objectPublisher
}
@@ -145,6 +145,12 @@ extension NSManagedObjectContext {
)
}
}
@nonobjc
internal func objectsDidChangeObserver<U: AnyObject>(remove: U) {
_ = self.userInfo(for: .objectsChangeObserver(U.self), initialize: { nil as Any? })
}
// MARK: Private
@@ -172,9 +178,9 @@ extension NSManagedObjectContext {
private func userInfo<T>(for key: UserInfoKeys, initialize: @escaping () -> T) -> T {
let keyString = key.keyString
if let value = self.userInfo[keyString] {
if let value = self.userInfo[keyString] as? T {
return value as! T
return value
}
let value = initialize()
self.userInfo[keyString] = value

View File

@@ -130,7 +130,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
return self
}
return Self.init(objectID: self.id, context: context)
return context.objectPublisher(objectID: self.id)
}
public func asReadOnly(in dataStack: DataStack) -> O? {
@@ -184,9 +184,9 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
// MARK: Internal
internal convenience init(objectID: O.ObjectID, context: NSManagedObjectContext) {
internal static func createUncached(objectID: O.ObjectID, context: NSManagedObjectContext) -> ObjectPublisher<O> {
self.init(
return self.init(
objectID: objectID,
context: context,
initializer: ObjectSnapshot<O>.init(objectID:context:)
@@ -195,6 +195,7 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
deinit {
self.context.objectsDidChangeObserver(remove: self)
self.observers.removeAllObjects()
}
@@ -221,30 +222,36 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
self.rawObjectWillChange = nil
}
self.$lazySnapshot.initialize({ initializer(objectID, context) })
context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (updatedIDs, deletedIDs) in
self.$lazySnapshot.initialize { [weak self] in
guard let self = self else {
return
return initializer(objectID, context)
}
if deletedIDs.contains(objectID) {
context.objectsDidChangeObserver(for: self).addObserver(self) { [weak self] (updatedIDs, deletedIDs) in
self.object = nil
guard let self = self else {
self.willChange()
self.$lazySnapshot.reset({ nil })
self.didChange()
self.notifyObservers()
}
else if updatedIDs.contains(objectID) {
self.willChange()
self.$lazySnapshot.reset({ initializer(objectID, context) })
self.didChange()
self.notifyObservers()
return
}
if deletedIDs.contains(objectID) {
self.object = nil
self.willChange()
self.$lazySnapshot.reset({ nil })
self.didChange()
self.notifyObservers()
}
else if updatedIDs.contains(objectID) {
self.willChange()
self.$lazySnapshot.reset({ initializer(objectID, context) })
self.didChange()
self.notifyObservers()
}
}
return initializer(objectID, context)
}
}
@@ -329,7 +336,6 @@ extension ObjectPublisher {
// MARK: - ObjectPublisher where O: NSManagedObject
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
extension ObjectPublisher where O: NSManagedObject {
// MARK: Public
@@ -337,11 +343,21 @@ extension ObjectPublisher where O: NSManagedObject {
/**
Returns the value for the property identified by a given key.
*/
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
public subscript<V: AllowedObjectiveCKeyPathValue>(dynamicMember member: KeyPath<O, V>) -> V {
fatalError()
// return self.snapshot[dynamicMember: member]
}
/**
Returns the value for the property identified by a given key.
*/
public func value<V: AllowedObjectiveCKeyPathValue>(forKeyPath keyPath: KeyPath<O, V>) -> V! {
let key = String(keyPath: keyPath)
return self.snapshot?.dictionaryForValues()[key] as! V?
}
}

View File

@@ -75,6 +75,29 @@ extension CoreStoreObject: ObjectRepresentation {}
extension DynamicObject where Self: ObjectRepresentation {
// MARK: Public
/**
An `ObjectPublisher` wrapper for the exact same object
*/
public func asPublisher() -> ObjectPublisher<Self>? {
return self.cs_toRaw()
.managedObjectContext
.map({ $0.objectPublisher(objectID: self.cs_id()) })
}
/**
A thread-safe `struct` that is a full-copy of the object's properties
*/
public func asSnapshot() -> ObjectSnapshot<Self>? {
return self.cs_toRaw()
.managedObjectContext
.flatMap({ ObjectSnapshot<Self>(objectID: self.cs_id(), context: $0) })
}
// MARK: ObjectRepresentation
public func objectID() -> Self.ObjectID {
@@ -85,7 +108,7 @@ extension DynamicObject where Self: ObjectRepresentation {
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<Self> {
let context = dataStack.unsafeContext()
return ObjectPublisher<Self>(objectID: self.cs_id(), context: context)
return context.objectPublisher(objectID: self.cs_id())
}
public func asReadOnly(in dataStack: DataStack) -> Self? {

View File

@@ -41,6 +41,14 @@ import AppKit
*/
@dynamicMemberLookup
public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
// MARK: Public
public func dictionaryForValues() -> [String: Any] {
return self.values
}
// MARK: ObjectRepresentation
@@ -54,7 +62,7 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<O> {
let context = dataStack.unsafeContext()
return ObjectPublisher<O>(objectID: self.id, context: context)
return context.objectPublisher(objectID: self.id)
}
public func asReadOnly(in dataStack: DataStack) -> O? {
@@ -107,14 +115,20 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
return nil
}
self.id = objectID
self.context = context
self.values = values
}
// MARK: FilePrivate
fileprivate var values: [String: Any]
// MARK: Private
private let id: O.ObjectID
private var values: [String: Any]
private let context: NSManagedObjectContext
private var valuesRef: NSDictionary {
@@ -125,17 +139,35 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
// MARK: - ObjectSnapshot where O: NSManagedObject
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
extension ObjectSnapshot where O: NSManagedObject {
/**
Returns the value for the property identified by a given key.
*/
@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)")
public subscript<V: AllowedObjectiveCKeyPathValue>(dynamicMember member: KeyPath<O, V>) -> V {
let key = String(keyPath: member)
return self.values[key] as! V
}
/**
Returns the value for the property identified by a given key.
*/
public func value<V: AllowedObjectiveCKeyPathValue>(forKeyPath keyPath: KeyPath<O, V>) -> V! {
let key = String(keyPath: keyPath)
return self.values[key] as! V?
}
/**
Mutates the value for the property identified by a given key.
*/
public mutating func setValue<V: AllowedObjectiveCKeyPathValue>(_ value: V!, forKeyPath keyPath: KeyPath<O, V>) {
let key = String(keyPath: keyPath)
self.values[key] = value
}
}
@@ -168,7 +200,7 @@ extension ObjectSnapshot where O: CoreStoreObject {
get {
let key = String(keyPath: member)
return self.values[key] as! V?
return self.values[key] as? V
}
set {
@@ -202,7 +234,7 @@ extension ObjectSnapshot where O: CoreStoreObject {
get {
let key = String(keyPath: member)
return self.values[key] as! V?
return self.values[key] as? V
}
set {
@@ -214,51 +246,59 @@ extension ObjectSnapshot where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToOne<D>>) -> D.ObjectID? {
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToOne<D>>) -> ObjectPublisher<D>? {
get {
let key = String(keyPath: member)
return self.values[key] as! D.ObjectID?
guard let id = self.values[key] as? D.ObjectID else {
return nil
}
return self.context.objectPublisher(objectID: id)
}
set {
let key = String(keyPath: member)
self.values[key] = newValue
self.values[key] = newValue?.objectID()
}
}
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyOrdered<D>>) -> [D.ObjectID] {
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyOrdered<D>>) -> [ObjectPublisher<D>] {
get {
let key = String(keyPath: member)
return self.values[key] as! [D.ObjectID]
let context = self.context
let ids = self.values[key] as! [D.ObjectID]
return ids.map(context.objectPublisher(objectID:))
}
set {
let key = String(keyPath: member)
self.values[key] = newValue
self.values[key] = newValue.map({ $0.objectID() })
}
}
/**
Returns the value for the property identified by a given key.
*/
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyUnordered<D>>) -> Set<D.ObjectID> {
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyUnordered<D>>) -> Set<ObjectPublisher<D>> {
get {
let key = String(keyPath: member)
return self.values[key] as! Set<D.ObjectID>
let context = self.context
let ids = self.values[key] as! Set<D.ObjectID>
return Set(ids.map(context.objectPublisher(objectID:)))
}
set {
let key = String(keyPath: member)
self.values[key] = newValue
self.values[key] = Set(newValue.map({ $0.objectID() }))
}
}
}

View 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
}
}

View 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)
}
}

View 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

View File

@@ -31,15 +31,20 @@ import CoreData
internal protocol RelationshipProtocol: PropertyProtocol {
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 }
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 rawObject: CoreStoreManagedObject? { get set }
var versionHashModifier: () -> String? { get }
var renamingIdentifier: () -> String? { get }
var minCount: Int { get }
var maxCount: Int { get }
var valueForSnapshot: Any { get }
var valueForSnapshot: Any? { get }
}

View 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
}
}

View 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
}
}

View File

@@ -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 as Any
}
// 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 as Any
}
// 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> {}

View 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
}
}

View 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
}
}

View File

@@ -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 as Any
}
// 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 as Any
}
// 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> {}

View File

@@ -1,296 +0,0 @@
//
// DiffableDataSource.CollectionView-AppKit.swift
// CoreStore
//
// Copyright © 2018 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(AppKit) && os(macOS)
import AppKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionView` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionView` may override some `NSCollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionView` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
item.setPerson(person)
return item
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.CollectionView` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionView<O: DynamicObject>: NSObject, NSCollectionViewDataSource {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
Initializes the `DiffableDataSource.CollectionView`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionView<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
item.setPerson(person)
return item
}
)
```
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionView`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
*/
@nonobjc
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
self.collectionView = collectionView
self.itemProvider = itemProvider
self.supplementaryViewProvider = supplementaryViewProvider
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
super.init()
collectionView.dataSource = self
}
/**
Reloads the `NSCollectionView` using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- parameter snapshot: the `ListSnapshot` used to reload the `UITableView` with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations will be applied as configured by the `defaultRowAnimation` value. Defaults to `true`.
*/
public func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
view: self.collectionView,
animatingDifferences: animatingDifferences,
performUpdates: { collectionView, changeset, setSections in
collectionView.reload(
using: changeset,
setData: setSections
)
}
)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
@nonobjc
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
@nonobjc
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: - NSCollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
return self.dispatcher.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dispatcher.numberOfItems(inSection: section)
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
guard let objectID = self.dispatcher.itemIdentifier(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let item = self.itemProvider(collectionView, indexPath, object) else {
Internals.abort("\(Internals.typeName(NSCollectionViewDataSource.self)) returned a `nil` item for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return item
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
return NSView()
}
return view
}
// MARK: Private
private weak var collectionView: NSCollectionView?
private let dataStack: DataStack
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
private let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}
// MARK: - NSCollectionView
extension NSCollectionView {
// MARK: FilePrivate
// Implementation based on https://github.com/ra1028/DiffableDataSources
@nonobjc
fileprivate func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if case .none = window, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.animator().performBatchUpdates(
{
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(IndexSet(changeset.sectionDeleted))
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(IndexSet(changeset.sectionInserted))
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(IndexSet(changeset.sectionUpdated))
}
for (source, target) in changeset.sectionMoved {
self.moveSection(source, toSection: target)
}
if !changeset.elementDeleted.isEmpty {
self.deleteItems(
at: Set(changeset.elementDeleted.map { IndexPath(item: $0.element, section: $0.section) })
)
}
if !changeset.elementInserted.isEmpty {
self.insertItems(
at: Set(changeset.elementInserted.map { IndexPath(item: $0.element, section: $0.section) })
)
}
if !changeset.elementUpdated.isEmpty {
self.reloadItems(
at: Set(changeset.elementUpdated.map { IndexPath(item: $0.element, section: $0.section) })
)
}
for (source, target) in changeset.elementMoved {
self.moveItem(
at: IndexPath(item: source.element, section: source.section),
to: IndexPath(item: target.element, section: target.section)
)
}
},
completionHandler: nil
)
}
}
}
#endif