Compare commits

...

28 Commits
7.0.0 ... 7.0.2

Author SHA1 Message Date
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
John Estropia
f3beca8769 fix compiler error in testcases 2019-10-25 16:17:25 +09:00
John Estropia
4baeb6d922 Fix weak linking of SwiftUI in podspec 2019-10-25 14:35:04 +09:00
John Estropia
98d860aff6 Add unit tests for List and Object Publishers 2019-10-25 14:34:22 +09:00
John Estropia
11a9e3991c changed ListPublisher and ObjectPublisher factory method naming to match ListMonitor and ObjectMonitor naming 2019-10-25 12:43:39 +09:00
John Estropia
f380d9dc25 ObjectSnapshot: allow dynamicMember keyPaths from superclasses 2019-10-25 12:36:13 +09:00
John Estropia
d546ff154f Merge pull request #344 from timfraedrich/patch-1
small correction to documentation
2019-10-23 19:27:33 +09:00
John Estropia
f21597d332 Merge pull request #341 from dmatushkin/develop
Fix for build on iOS with Swift Package Manager
2019-10-23 19:27:01 +09:00
Tim Fraedrich
56d9719984 small correction to documentation
While using some code from the documentation that led to issues I noticed that some parts of it are not accurate anymore, so I corrected a few small things that came to my attention. Maybe it is worth having an even closer look though.
2019-10-04 14:19:14 +00:00
dmatushkin
6d75dcbc32 Fix for build on iOS with Swift Package Manager 2019-09-15 14:57:20 +04:00
38 changed files with 2161 additions and 1820 deletions

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "7.0.0"
s.version = "7.0.2"
s.swift_version = "5.1"
s.license = "MIT"
s.homepage = "https://github.com/JohnEstropia/CoreStore"
@@ -18,5 +18,5 @@ Pod::Spec.new do |s|
s.public_header_files = "Sources/**/*.h"
s.frameworks = "Foundation", "CoreData"
s.requires_arc = true
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine' }
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine -weak_framework SwiftUI' }
end

View File

@@ -141,6 +141,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 +472,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 */; };
@@ -522,6 +529,9 @@
B580857A1CDF808C004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
B581B9322362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */; };
B581B9332362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */; };
B581B9342362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */; };
B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
@@ -580,10 +590,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 */; };
@@ -597,10 +607,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 */; };
@@ -665,6 +675,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 */; };
@@ -903,6 +914,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>"; };
@@ -973,7 +985,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; };
@@ -994,6 +1006,7 @@
B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = "<group>"; };
B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = "<group>"; };
B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = "<group>"; };
B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisherTests.swift; sourceTree = "<group>"; };
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeProtocol.swift; sourceTree = "<group>"; };
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipProtocol.swift; sourceTree = "<group>"; };
B5831B791F34ACBA00A9F647 /* Transformable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.swift; sourceTree = "<group>"; };
@@ -1010,14 +1023,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>"; };
@@ -1038,6 +1051,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>"; };
@@ -1262,6 +1276,7 @@
B5D8CA7A2346EC550055D7D1 /* ListPublisherTests.swift */,
B5DC47C51C93D22900FA3BF3 /* MigrationChainTests.swift */,
B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */,
B581B9312362BB8C002BDB2B /* ObjectPublisherTests.swift */,
B52557771D02826E00E51965 /* OrderByTests.swift */,
B57D27C11D0BC20100539C58 /* QueryTests.swift */,
B52557831D02A07400E51965 /* SectionByTests.swift */,
@@ -1602,9 +1617,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>";
@@ -2084,6 +2101,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5BF7FB7234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift in Sources */,
B5DE5230230BDA1300A22534 /* CoreStoreDefaults.swift in Sources */,
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
@@ -2116,12 +2134,11 @@
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 */,
B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
@@ -2146,7 +2163,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 */,
@@ -2219,6 +2236,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 */,
@@ -2235,6 +2253,7 @@
B512607F1E97A18000402229 /* CoreStoreObject+Convenience.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 */,
@@ -2285,6 +2304,7 @@
B5220E0C1D0D0D19009BC71E /* ImportTests.swift in Sources */,
B5D339B41E925C2B00C880DE /* DynamicModelTests.swift in Sources */,
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */,
B581B9322362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */,
B52557881D02DE8100E51965 /* FetchTests.swift in Sources */,
B5489F501CF603D5008B4978 /* FromTests.swift in Sources */,
B52557781D02826E00E51965 /* OrderByTests.swift in Sources */,
@@ -2310,7 +2330,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 */,
@@ -2381,6 +2401,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 */,
@@ -2429,6 +2450,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 */,
@@ -2468,7 +2490,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 */,
@@ -2486,7 +2508,7 @@
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 */,
);
@@ -2508,6 +2530,7 @@
B5220E0D1D0D0D19009BC71E /* ImportTests.swift in Sources */,
B5D339B51E925C2B00C880DE /* DynamicModelTests.swift in Sources */,
B525576D1CFAF18F00E51965 /* IntoTests.swift in Sources */,
B581B9332362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */,
B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */,
B52557891D02DE8100E51965 /* FetchTests.swift in Sources */,
B5489F511CF603D5008B4978 /* FromTests.swift in Sources */,
@@ -2531,9 +2554,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 */,
@@ -2574,7 +2600,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 */,
@@ -2602,6 +2627,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 +2676,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 */,
@@ -2691,7 +2718,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 */,
@@ -2709,7 +2735,6 @@
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 */,
);
@@ -2731,6 +2756,7 @@
B525576E1CFAF18F00E51965 /* IntoTests.swift in Sources */,
B5D339B61E925C2B00C880DE /* DynamicModelTests.swift in Sources */,
B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */,
B581B9342362BB8C002BDB2B /* ObjectPublisherTests.swift in Sources */,
B525578A1D02DE8100E51965 /* FetchTests.swift in Sources */,
B5220E281D1308E5009BC71E /* SectionByTests.swift in Sources */,
B5489F521CF603D5008B4978 /* FromTests.swift in Sources */,
@@ -2756,7 +2782,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 */,
@@ -2827,6 +2853,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 */,
@@ -2875,6 +2902,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 */,
@@ -2914,7 +2942,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 */,
@@ -2932,7 +2960,7 @@
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 */,
);
@@ -3105,7 +3133,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.0;
MARKETING_VERSION = 7.0.2;
OTHER_LDFLAGS = (
"-weak_framework",
Combine,
@@ -3128,7 +3156,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.0;
MARKETING_VERSION = 7.0.2;
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

@@ -79,7 +79,7 @@ struct ColorsDemo {
static let palettes: ListPublisher<Palette> = {
return ColorsDemo.stack.listPublisher(
return ColorsDemo.stack.publishList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))

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

@@ -31,7 +31,7 @@ final class SwiftUIContainerViewController: UIViewController {
let hostingController = UIHostingController(
rootView: SwiftUIView(
palettes: ColorsDemo.stack.listPublisher(
palettes: ColorsDemo.stack.publishList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))

View File

@@ -171,7 +171,7 @@ struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView(
palettes: ColorsDemo.stack.listPublisher(
palettes: ColorsDemo.stack.publishList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))

View File

@@ -41,99 +41,26 @@ class ListPublisherTests: BaseTestDataTestCase {
self.prepareStack { (stack) in
// let observer = TestListObserver()
let listPublisher = stack.listPublisher(
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
// monitor.addObserver(observer)
//
// XCTAssertFalse(monitor.hasSections())
// XCTAssertFalse(monitor.hasObjects())
// XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
//
// var events = 0
//
// let willChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 0)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 0
// }
// )
// let didInsertSectionExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 1)
// XCTAssertEqual(
// ((note.userInfo as NSDictionary?) ?? [:]),
// [
// "sectionInfo": monitor.sectionInfo(at: 0),
// "sectionIndex": 0
// ] as NSDictionary
// )
// defer {
//
// events += 1
// }
// return events == 1
// }
// )
// let didInsertObjectExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 2)
//
// let userInfo = note.userInfo
// XCTAssertNotNil(userInfo)
// XCTAssertEqual(
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
// ["indexPath", "object"]
// )
//
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
// XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
//
// let object = userInfo?["object"] as? TestEntity1
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
// XCTAssertEqual(object?.testNumber, NSNumber(value: 1))
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
// XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
// defer {
//
// events += 1
// }
// return events == 2
// }
// )
// let didChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 3
// }
// )
XCTAssertFalse(listPublisher.snapshot.hasSections())
XCTAssertFalse(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.itemIDs.isEmpty)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
@@ -161,522 +88,216 @@ class ListPublisherTests: BaseTestDataTestCase {
self.waitAndCheckExpectations()
withExtendedLifetime(listPublisher, {})
withExtendedLifetime(observer, {})
}
}
// @objc
// dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
//
// self.prepareStack { (stack) in
//
// self.prepareTestDataForStack(stack)
//
// let observer = TestListObserver()
// let monitor = stack.monitorSectionedList(
// From<TestEntity1>(),
// SectionBy(#keyPath(TestEntity1.testBoolean)),
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
// )
// monitor.addObserver(observer)
//
// XCTAssertTrue(monitor.hasSections())
// XCTAssertEqual(monitor.numberOfSections(), 2)
// XCTAssertTrue(monitor.hasObjects())
// XCTAssertTrue(monitor.hasObjects(in: 0))
// XCTAssertEqual(monitor.numberOfObjects(in: 0), 2)
// XCTAssertEqual(monitor.numberOfObjects(in: 1), 3)
//
// var events = 0
//
// let willChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 0)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 0
// }
// )
//
// let didUpdateObjectExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssert(events == 1 || events == 2)
//
// let userInfo = note.userInfo
// XCTAssertNotNil(userInfo)
// XCTAssertEqual(
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
// ["indexPath", "object"]
// )
//
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
// let object = userInfo?["object"] as? TestEntity1
//
// switch object?.testEntityID {
//
// case NSNumber(value: 101)?:
// XCTAssertEqual(indexPath?.index(atPosition: 0), 1)
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
//
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
// XCTAssertEqual(object?.testNumber, NSNumber(value: 11))
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
// XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
//
// case NSNumber(value: 102)?:
// XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
// XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
//
// XCTAssertEqual(object?.testBoolean, NSNumber(value: false))
// XCTAssertEqual(object?.testNumber, NSNumber(value: 22))
// XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
// XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
// XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
// XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
//
// default:
// XCTFail()
// }
// defer {
//
// events += 1
// }
// return events == 1 || events == 2
// }
// )
// let didChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 3)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 3
// }
// )
// let saveExpectation = self.expectation(description: "save")
// stack.perform(
// asynchronous: { (transaction) -> Bool in
//
// if let object = try transaction.fetchOne(
// From<TestEntity1>(),
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
//
// object.testNumber = NSNumber(value: 11)
// object.testDecimal = NSDecimalNumber(string: "11")
// object.testString = "nil:TestEntity1:11"
// object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
// object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
// }
// else {
//
// XCTFail()
// }
// if let object = try transaction.fetchOne(
// From<TestEntity1>(),
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
//
// object.testNumber = NSNumber(value: 22)
// object.testDecimal = NSDecimalNumber(string: "22")
// object.testString = "nil:TestEntity1:22"
// object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
// object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
// }
// else {
//
// XCTFail()
// }
// return transaction.hasChanges
// },
// success: { (hasChanges) in
//
// XCTAssertTrue(hasChanges)
// saveExpectation.fulfill()
// },
// failure: { _ in
//
// XCTFail()
// }
// )
// self.waitAndCheckExpectations()
// }
// }
//
// @objc
// dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
//
// self.prepareStack { (stack) in
//
// self.prepareTestDataForStack(stack)
//
// let observer = TestListObserver()
// let monitor = stack.monitorSectionedList(
// From<TestEntity1>(),
// SectionBy(#keyPath(TestEntity1.testBoolean)),
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
// )
// monitor.addObserver(observer)
//
// var events = 0
//
// let willChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 0)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 0
// }
// )
// let didMoveObjectExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 1)
//
// let userInfo = note.userInfo
// XCTAssertNotNil(userInfo)
// XCTAssertEqual(
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
// ["fromIndexPath", "toIndexPath", "object"]
// )
//
// let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
// XCTAssertEqual(fromIndexPath?.index(atPosition: 0), 0)
// XCTAssertEqual(fromIndexPath?.index(atPosition: 1), 0)
//
// let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
// XCTAssertEqual(toIndexPath?.index(atPosition: 0), 1)
// XCTAssertEqual(toIndexPath?.index(atPosition: 1), 1)
//
// let object = userInfo?["object"] as? TestEntity1
// XCTAssertEqual(object?.testEntityID, NSNumber(value: 102))
// XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
//
// defer {
//
// events += 1
// }
// return events == 1
// }
// )
// let didChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 2)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 2
// }
// )
// let saveExpectation = self.expectation(description: "save")
// stack.perform(
// asynchronous: { (transaction) -> Bool in
//
// if let object = try transaction.fetchOne(
// From<TestEntity1>(),
// Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
//
// object.testBoolean = NSNumber(value: true)
// }
// else {
//
// XCTFail()
// }
// return transaction.hasChanges
// },
// success: { (hasChanges) in
//
// XCTAssertTrue(hasChanges)
// saveExpectation.fulfill()
// },
// failure: { _ in
//
// XCTFail()
// }
// )
// self.waitAndCheckExpectations()
// }
// }
//
// @objc
// dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
//
// self.prepareStack { (stack) in
//
// self.prepareTestDataForStack(stack)
//
// let observer = TestListObserver()
// let monitor = stack.monitorSectionedList(
// From<TestEntity1>(),
// SectionBy(#keyPath(TestEntity1.testBoolean)),
// OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
// )
// monitor.addObserver(observer)
//
// var events = 0
//
// let willChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 0)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 0
// }
// )
// let didUpdateObjectExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssert(events == 1 || events == 2)
//
// let userInfo = note.userInfo
// XCTAssertNotNil(userInfo)
// XCTAssertEqual(
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
// ["indexPath", "object"]
// )
//
// let indexPath = userInfo?["indexPath"] as? NSIndexPath
//
// XCTAssertEqual(indexPath?.section, 0)
// XCTAssert(indexPath?.index(atPosition: 1) == 0 || indexPath?.index(atPosition: 1) == 1)
//
// let object = userInfo?["object"] as? TestEntity1
// XCTAssertEqual(object?.isDeleted, true)
//
// defer {
//
// events += 1
// }
// return events == 1 || events == 2
// }
// )
// let didDeleteSectionExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 3)
//
// let userInfo = note.userInfo
// XCTAssertNotNil(userInfo)
// XCTAssertEqual(
// Set(userInfo?.keys.map({ $0 as! String }) ?? []),
// ["sectionInfo", "sectionIndex"]
// )
//
// let sectionInfo = userInfo?["sectionInfo"] as? NSFetchedResultsSectionInfo
// XCTAssertNotNil(sectionInfo)
// XCTAssertEqual(sectionInfo?.name, "0")
//
// let sectionIndex = userInfo?["sectionIndex"]
// XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(value: 0))
//
// defer {
//
// events += 1
// }
// return events == 3
// }
// )
// let didChangeExpectation = self.expectation(
// forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
// object: observer,
// handler: { (note) -> Bool in
//
// XCTAssertEqual(events, 4)
// XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
// defer {
//
// events += 1
// }
// return events == 4
// }
// )
// let saveExpectation = self.expectation(description: "save")
// stack.perform(
// asynchronous: { (transaction) -> Bool in
//
// let count = try transaction.deleteAll(
// From<TestEntity1>(),
// Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
// )
// XCTAssertEqual(count, 2)
// return transaction.hasChanges
// },
// success: { (hasChanges) in
//
// XCTAssertTrue(hasChanges)
// saveExpectation.fulfill()
// },
// failure: { _ in
//
// XCTFail()
// }
// )
// self.waitAndCheckExpectations()
// }
// }
//}
//
//
//// MARK: TestListObserver
//
//@available(macOS 10.12, *)
//class TestListObserver: ListSectionObserver {
//
// // MARK: ListObserver
//
// typealias ListEntityType = TestEntity1
//
// func listMonitorWillChange(_ monitor: ListMonitor<TestEntity1>) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitorWillChange:"),
// object: self,
// userInfo: [:]
// )
// }
//
// func listMonitorDidChange(_ monitor: ListMonitor<TestEntity1>) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitorDidChange:"),
// object: self,
// userInfo: [:]
// )
// }
//
// func listMonitorWillRefetch(_ monitor: ListMonitor<TestEntity1>) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitorWillRefetch:"),
// object: self,
// userInfo: [:]
// )
// }
//
// func listMonitorDidRefetch(_ monitor: ListMonitor<TestEntity1>) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitorDidRefetch:"),
// object: self,
// userInfo: [:]
// )
// }
//
//
// // MARK: ListObjectObserver
//
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: IndexPath) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
// object: self,
// userInfo: [
// "object": object,
// "indexPath": indexPath
// ]
// )
// }
//
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: IndexPath) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
// object: self,
// userInfo: [
// "object": object,
// "indexPath": indexPath
// ]
// )
// }
//
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: IndexPath) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
// object: self,
// userInfo: [
// "object": object,
// "indexPath": indexPath
// ]
// )
// }
//
//
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
// object: self,
// userInfo: [
// "object": object,
// "fromIndexPath": fromIndexPath,
// "toIndexPath": toIndexPath
// ]
// )
// }
//
//
// // MARK: ListSectionObserver
//
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
// object: self,
// userInfo: [
// "sectionInfo": sectionInfo,
// "sectionIndex": sectionIndex
// ]
// )
// }
//
// func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
//
// NotificationCenter.default.post(
// name: Notification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
// object: self,
// userInfo: [
// "sectionInfo": sectionInfo,
// "sectionIndex": sectionIndex
// ]
// )
// }
@objc
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
if let object = try transaction.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
object.testNumber = NSNumber(value: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
}
else {
XCTFail()
}
if let object = try transaction.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testNumber = NSNumber(value: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(listPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 4)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
if let object = try transaction.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testBoolean = NSNumber(value: true)
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(listPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 1)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 3)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let count = try transaction.deleteAll(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
)
XCTAssertEqual(count, 2)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
}
}
}
#endif

View File

@@ -0,0 +1,154 @@
//
// ObjectPublisherTests.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.
//
import XCTest
@testable
import CoreStore
// MARK: - ObjectPublisherTests
@available(macOS 10.12, *)
class ObjectPublisherTests: BaseTestDataTestCase {
@objc
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
guard let object = try stack.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
XCTFail()
return
}
let observer = NSObject()
let objectPublisher = stack.publishObject(object)
XCTAssertEqual(objectPublisher.object, object)
XCTAssertNotNil(objectPublisher.snapshot)
let didChangeExpectation = self.expectation(description: "didChange")
objectPublisher.addObserver(observer) { objectPublisher in
XCTAssertEqual(objectPublisher.object?.testNumber, NSNumber(value: 10))
XCTAssertEqual(objectPublisher.object?.testString, "nil:TestEntity1:10")
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
guard let object = transaction.edit(object) else {
XCTFail()
try transaction.cancel()
}
object.testNumber = NSNumber(value: 10)
object.testString = "nil:TestEntity1:10"
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(objectPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
guard let object = try stack.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
XCTFail()
return
}
let observer = NSObject()
let objectPublisher = stack.publishObject(object)
XCTAssertEqual(objectPublisher.object, object)
XCTAssertNotNil(objectPublisher.snapshot)
let didChangeExpectation = self.expectation(description: "didChange")
objectPublisher.addObserver(observer) { objectPublisher in
XCTAssertNil(objectPublisher.object)
XCTAssertNil(objectPublisher.snapshot)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
guard let object = transaction.edit(object) else {
XCTFail()
try transaction.cancel()
}
transaction.delete(object)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(objectPublisher, {})
withExtendedLifetime(observer, {})
}
}
}

View File

@@ -45,8 +45,9 @@ let package = Package(
.testTarget(
name: "CoreStoreTests",
dependencies: ["CoreStore"],
path: "CoreStoreTests"
path: "CoreStoreTests",
exclude: ["*.h", "*.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
@@ -1552,10 +1552,10 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:
```swift
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: IndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: IndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: IndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: IndexPath, toIndexPath: IndexPath)
```
- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:
```swift
@@ -1612,9 +1612,9 @@ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -
}
```
To access the objects of a sectioned list, use an `NSIndexPath` or a tuple:
To access the objects of a sectioned list, use an `IndexPath` or a tuple:
```swift
let indexPath = NSIndexPath(forRow: 2, inSection: 1)
let indexPath = IndexPath(row: 2, section: 1)
let person1 = self.monitor[indexPath]
let person2 = self.monitor[1, 2]
// person1 and person2 are the same object

View File

@@ -43,5 +43,5 @@ internal protocol AttributeProtocol: PropertyProtocol {
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

@@ -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 {
@@ -1160,7 +1279,7 @@ extension NSEntityDescription: CoreStoreDebugStringConvertible {
info.append(("compoundIndexes", self.compoundIndexes))
}
if #available(macOS 10.11, *) {
if #available(macOS 10.11, iOS 9.0, *) {
info.append(("uniquenessConstraints", self.uniquenessConstraints))
}

View File

@@ -452,7 +452,7 @@ public final class CoreStoreSchema: DynamicSchema {
}
for (entity, entityDescription) in entityDescriptionsByEntity {
if #available(macOS 10.11, *) {
if #available(macOS 10.11, iOS 9.0, *) {
let uniqueConstraints = entity.uniqueConstraints.filter({ !$0.isEmpty })
if !uniqueConstraints.isEmpty {

View File

@@ -39,9 +39,21 @@ extension DataStack {
- parameter object: the `DynamicObject` to observe changes from
- returns: an `ObjectPublisher` that broadcasts changes to `object`
*/
public func objectPublisher<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
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)
}
/**
@@ -51,9 +63,9 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
public func publishList<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.listPublisher(from, fetchClauses)
return self.publishList(from, fetchClauses)
}
/**
@@ -63,7 +75,7 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
public func publishList<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return ListPublisher(
dataStack: self,
@@ -99,9 +111,9 @@ extension DataStack {
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func listPublisher<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
public func publishList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.listPublisher(
return self.publishList(
clauseChain.from,
clauseChain.fetchClauses
)
@@ -115,9 +127,9 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
public func publishList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.listPublisher(
return self.publishList(
from,
sectionBy,
fetchClauses
@@ -132,7 +144,7 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
public func publishList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return ListPublisher(
dataStack: self,
@@ -169,14 +181,59 @@ extension DataStack {
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func listPublisher<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
public func publishList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.listPublisher(
return self.publishList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
// MARK: - Deprecated
@available(*, deprecated, renamed: "publishObject(_:)")
public func objectPublisher<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
return self.publishObject(object)
}
@available(*, deprecated, renamed: "publishList(_:_:)")
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.publishList(from, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:_:)")
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return self.publishList(from, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:)")
public func listPublisher<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(clauseChain)
}
@available(*, deprecated, renamed: "publishList(_:_:_:)")
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.publishList(from, sectionBy, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:_:_:)")
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return self.publishList(from, sectionBy, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:)")
public func listPublisher<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(clauseChain)
}
}
#endif

View File

@@ -614,7 +614,7 @@ extension DataStack {
}
let fileManager = FileManager.default
let systemTemporaryDirectoryURL: URL
if #available(macOS 10.12, *) {
if #available(macOS 10.12, iOS 10.0, *) {
systemTemporaryDirectoryURL = fileManager.temporaryDirectory
}

View File

@@ -0,0 +1,216 @@
//
// 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.
//
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>
}
}

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,211 @@
//
// 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.
//
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
)
}
}
}

View File

@@ -28,7 +28,7 @@
// 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
@@ -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,7 +348,7 @@ 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?) {
@@ -337,7 +377,7 @@ extension Internals {
mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID) {
guard let itemPosition = self.itemPositionMap()[beforeItemID] else {
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
Internals.abort("Item \(beforeItemID) does not exist")
}
@@ -348,7 +388,7 @@ extension Internals {
mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: 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<C: Collection>(itemIDs: C) where C.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,7 +477,9 @@ extension Internals {
}
self.sections[itemPosition.sectionIndex]
.elements[itemPosition.itemRelativeIndex].isReloaded = true
newItemIDs.insert(itemID)
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func append(sectionIDs: [String]) {
@@ -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)
}
}
@@ -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
}
@@ -120,21 +120,37 @@ extension NSManagedObjectContext {
return (updated: [], deleted: [])
}
if userInfo[NSInvalidatedAllObjectsKey] != nil {
let context = notification.object as! NSManagedObjectContext
return (updated: Set(context.registeredObjects.map({ $0.objectID })), deleted: [])
}
var updatedObjectIDs: Set<NSManagedObjectID> = []
if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObjectID> {
if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject> {
updatedObjectIDs.formUnion(updatedObjects)
updatedObjectIDs.formUnion(updatedObjects.map({ $0.objectID }))
}
if let mergedObjects = userInfo[NSRefreshedObjectsKey] as? Set<NSManagedObjectID> {
if let mergedObjects = userInfo[NSRefreshedObjectsKey] as? Set<NSManagedObject> {
updatedObjectIDs.formUnion(mergedObjects)
updatedObjectIDs.formUnion(mergedObjects.map({ $0.objectID }))
}
let deletedObjectIDs: Set<NSManagedObjectID> = (userInfo[NSDeletedObjectsKey] as? Set<NSManagedObjectID>) ?? []
return (updated: updatedObjectIDs, deleted: deletedObjectIDs)
if let mergedObjects = userInfo[NSInvalidatedObjectsKey] as? Set<NSManagedObject> {
updatedObjectIDs.formUnion(mergedObjects.map({ $0.objectID }))
}
let deletedObjectIDs: Set<NSManagedObject> = (userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? []
return (updated: updatedObjectIDs, deleted: Set(deletedObjectIDs.map({ $0.objectID })))
}
)
}
}
@nonobjc
internal func objectsDidChangeObserver<U: AnyObject>(remove: U) {
_ = self.userInfo(for: .objectsChangeObserver(U.self), initialize: { nil as Any? })
}
// MARK: Private
@@ -162,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?
}
}
@@ -354,7 +370,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Required<V>>) -> V? {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Required<V>>) -> V? {
return self.object?[keyPath: member].value
}
@@ -362,7 +378,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Optional<V>>) -> V? {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Optional<V>>) -> V? {
return self.object?[keyPath: member].value
}
@@ -370,7 +386,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Required<V>>) -> V? {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Required<V>>) -> V? {
return self.object?[keyPath: member].value
}
@@ -378,7 +394,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Optional<V>>) -> V? {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Optional<V>>) -> V? {
return self.object?[keyPath: member].value
}
@@ -386,7 +402,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToOne<D>>) -> D? {
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToOne<D>>) -> D? {
return self.object?[keyPath: member].value
}
@@ -394,7 +410,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToManyOrdered<D>>) -> [D]? {
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyOrdered<D>>) -> [D]? {
return self.object?[keyPath: member].value
}
@@ -402,7 +418,7 @@ extension ObjectPublisher where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.ToManyUnordered<D>>) -> Set<D>? {
public subscript<OBase, D>(dynamicMember member: KeyPath<O, RelationshipContainer<OBase>.ToManyUnordered<D>>) -> Set<D>? {
return self.object?[keyPath: member].value
}

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
}
}
@@ -146,7 +178,7 @@ extension ObjectSnapshot where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Required<V>>) -> V {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Required<V>>) -> V {
get {
@@ -163,12 +195,12 @@ extension ObjectSnapshot where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, ValueContainer<O>.Optional<V>>) -> V? {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, ValueContainer<OBase>.Optional<V>>) -> V? {
get {
let key = String(keyPath: member)
return self.values[key] as! V?
return self.values[key] as? V
}
set {
@@ -180,7 +212,7 @@ extension ObjectSnapshot where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Required<V>>) -> V {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Required<V>>) -> V {
get {
@@ -197,12 +229,12 @@ extension ObjectSnapshot where O: CoreStoreObject {
/**
Returns the value for the property identified by a given key.
*/
public subscript<V>(dynamicMember member: KeyPath<O, TransformableContainer<O>.Optional<V>>) -> V? {
public subscript<OBase, V>(dynamicMember member: KeyPath<O, TransformableContainer<OBase>.Optional<V>>) -> V? {
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<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.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<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.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<D>(dynamicMember member: KeyPath<O, RelationshipContainer<O>.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

@@ -313,9 +313,9 @@ public enum RelationshipContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return self.value?.objectID() as Any
return self.value?.objectID()
}
@@ -609,9 +609,9 @@ public enum RelationshipContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return self.value.map({ $0.objectID() }) as Any
return self.value.map({ $0.objectID() })
}
@@ -910,9 +910,9 @@ public enum RelationshipContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return Set(self.value.map({ $0.objectID() })) as Any
return Set(self.value.map({ $0.objectID() }))
}

View File

@@ -41,5 +41,5 @@ internal protocol RelationshipProtocol: PropertyProtocol {
var renamingIdentifier: () -> String? { get }
var minCount: Int { get }
var maxCount: Int { get }
var valueForSnapshot: Any { get }
var valueForSnapshot: Any? { get }
}

View File

@@ -272,9 +272,9 @@ public enum TransformableContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return self.value as Any
return self.value
}
@@ -494,9 +494,9 @@ public enum TransformableContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return self.value as Any
return self.value
}

View File

@@ -267,9 +267,9 @@ public enum ValueContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return self.value as Any
return self.value
}
@@ -489,9 +489,9 @@ public enum ValueContainer<O: CoreStoreObject> {
}
}
internal var valueForSnapshot: Any {
internal var valueForSnapshot: Any? {
return self.value as Any
return self.value
}

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