back-portable TableView DiffableDataSource

This commit is contained in:
John Estropia
2019-10-13 11:45:30 +09:00
parent 12c58e3955
commit f5a165d47d
20 changed files with 2031 additions and 636 deletions

View File

@@ -105,6 +105,26 @@
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */; };
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */; };
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
B50E175323517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
B50E175423517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
B50E175523517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */; };
B50E175723517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
B50E175823517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
B50E175923517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175623517DE4004F033C /* Differentiable.swift */; };
B50E175C2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */; };
B50E17612351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
B50E17622351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
B50E17632351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
B50E17642351FA66004F033C /* Internals.Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50E17602351FA66004F033C /* Internals.Closure.swift */; };
B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
@@ -572,10 +592,10 @@
B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */; };
B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */; };
B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */; };
B5BF7FBC234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
B5BF7FBD234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
B5BF7FBE234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; };
B5BF7FBF234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.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 */; };
B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */; };
B5BF7FC1234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; };
B5BF7FC2234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; };
B5BF7FC3234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; };
@@ -878,6 +898,11 @@
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; };
B50564D22350CC3100482308 /* PropertyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PropertyProtocol.swift; sourceTree = "<group>"; };
B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableAttributeType.swift; sourceTree = "<group>"; };
B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.StagedChangeset.swift; sourceTree = "<group>"; };
B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.Changeset.swift; sourceTree = "<group>"; };
B50E175623517DE4004F033C /* Differentiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Differentiable.swift; sourceTree = "<group>"; };
B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.DiffResult.swift; sourceTree = "<group>"; };
B50E17602351FA66004F033C /* Internals.Closure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.Closure.swift; sourceTree = "<group>"; };
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+DataSources.swift"; sourceTree = "<group>"; };
B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStoreObject+Convenience.swift"; sourceTree = "<group>"; };
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+DynamicModel.swift"; sourceTree = "<group>"; };
@@ -994,7 +1019,7 @@
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.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.TableView.swift; sourceTree = "<group>"; };
B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.FallbackDiffableDataUIDispatcher.swift; sourceTree = "<group>"; };
B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataUIDispatcher.swift; sourceTree = "<group>"; };
B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveObject.swift; sourceTree = "<group>"; };
B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = "<group>"; };
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.LazyNonmutating.swift; sourceTree = "<group>"; };
@@ -1316,6 +1341,7 @@
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */,
B50564D22350CC3100482308 /* PropertyProtocol.swift */,
B53D9E5823513712000F48FB /* DiffableDataSourceSnapshotProtocol.swift */,
B50E175623517DE4004F033C /* Differentiable.swift */,
);
name = Protocols;
sourceTree = "<group>";
@@ -1714,13 +1740,17 @@
B5474D142227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift */,
B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */,
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */,
B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */,
B5BF7FBB234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift */,
B50E174C23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift */,
B50E175123517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift */,
B50E175B2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift */,
B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */,
B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */,
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */,
B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */,
B5E84F2B1AFF849C0064E85B /* Internals.NotificationObserver.swift */,
B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */,
B50E17602351FA66004F033C /* Internals.Closure.swift */,
B5DE522A230BD7CC00A22534 /* Internals.swift */,
B5E84F2D1AFF849C0064E85B /* Internals.WeakObject.swift */,
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */,
@@ -2063,6 +2093,7 @@
B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
B5ECDBF91CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
B5CA2B081F7E5ACA004B1936 /* WhereClauseType.swift in Sources */,
B50E17612351FA66004F033C /* Internals.Closure.swift in Sources */,
B5C976E71C6E3A5A00B1AF90 /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
B56923F51EB828BF007C4DC9 /* CSDynamicSchema.swift in Sources */,
B5F1DA901B9AA991007C5CBB /* ImportableUniqueObject.swift in Sources */,
@@ -2114,6 +2145,7 @@
B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */,
B5E1B5931CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
B5277677234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */,
B50E175C2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5ECDC291CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
B56923F01EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */,
B5F335582348D75D00FD649F /* LiveResult.swift in Sources */,
@@ -2121,6 +2153,7 @@
B546F9581C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */,
B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */,
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */,
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */,
B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */,
@@ -2160,6 +2193,7 @@
B596BBB61DD5BC67001DCDD9 /* FetchableSource.swift in Sources */,
B5E1B5A21CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */,
B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */,
B50E175723517DE4004F033C /* Differentiable.swift in Sources */,
B53304AA230BA4F7007C2BD8 /* DynamicObjectMeta.swift in Sources */,
B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */,
B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */,
@@ -2177,6 +2211,7 @@
B5FAD6A91B50A4B400714891 /* Progress+Convenience.swift in Sources */,
B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */,
B5215CA91FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B50E174D23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
B5E222231CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */,
B52F744A1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
@@ -2185,7 +2220,7 @@
B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */,
B56923E81EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B275F1EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5BF7FBC234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
B5BF7FBC234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5215CA41FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
@@ -2288,7 +2323,7 @@
82BA18B21C4BBD3900A0916E /* ImportableObject.swift in Sources */,
82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */,
82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */,
B5BF7FBD234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
B5BF7FBD234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5D339D91E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
82BA18CE1C4BBD7100A0916E /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
B56923FB1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
@@ -2307,6 +2342,7 @@
B5A992201EA898720091A2E3 /* UserInfo.swift in Sources */,
B5FE4DAD1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */,
B5D339E31E948C3600C880DE /* Value.swift in Sources */,
B50E175323517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
82BA18C51C4BBD5300A0916E /* ListObserver.swift in Sources */,
B53FBA0D1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
82BA18C21C4BBD5300A0916E /* ObjectMonitor.swift in Sources */,
@@ -2324,6 +2360,7 @@
B5E1B5AA1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5E1B59F1CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
B50E17622351FA66004F033C /* Internals.Closure.swift in Sources */,
B5ECDC251CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
B549F6741E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
@@ -2396,11 +2433,13 @@
B5E222251CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
82BA18C41C4BBD5300A0916E /* ListMonitor.swift in Sources */,
B5215CAA1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B50E175823517DE4004F033C /* Differentiable.swift in Sources */,
82BA18BA1C4BBD4A00A0916E /* Select.swift in Sources */,
B52F744B1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
B5AEFAB61C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
B5E2222C1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */,
B50E174E23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
82BA18D81C4BBD7100A0916E /* Internals.WeakObject.swift in Sources */,
B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
@@ -2436,6 +2475,7 @@
B52F743E1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
18166884232B9ED00097C275 /* KeyPath+KeyPaths.swift in Sources */,
B5E8A72121C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
B50E175D2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5474D162227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
B501322B2346A9AE00FC238B /* LiveList.swift in Sources */,
B56924001EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */,
@@ -2507,7 +2547,7 @@
B546F9761C9C553300D5AC55 /* SetupResult.swift in Sources */,
B53FBA161CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
B5ECDC271CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
B5BF7FBF234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
B5BF7FBF234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5D339DB1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
B52DD1951BE1F92500949AFE /* CoreStoreError.swift in Sources */,
B56923FD1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
@@ -2526,6 +2566,7 @@
B5A992221EA898720091A2E3 /* UserInfo.swift in Sources */,
B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */,
B5D339E51E948C3600C880DE /* Value.swift in Sources */,
B50E175523517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
B5A5F26A1CAEC50F004AB9AF /* CSSelect.swift in Sources */,
B5220E1B1D13079B009BC71E /* CSCoreStore+Observing.swift in Sources */,
B5FEC1911C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */,
@@ -2543,6 +2584,7 @@
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */,
B559CD471CAA8B6300E4D58B /* CSSetupResult.swift in Sources */,
B50E17642351FA66004F033C /* Internals.Closure.swift in Sources */,
B5ECDBF01CA6BF2000C7F112 /* CSFrom.swift in Sources */,
B549F6761E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
@@ -2615,11 +2657,13 @@
B5220E1A1D130791009BC71E /* Internals.CoreStoreFetchedResultsController.swift in Sources */,
B5215CAC1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B53FBA0F1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
B50E175A23517DE4004F033C /* Differentiable.swift in Sources */,
B52F744D1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
B52DD19A1BE1F92800949AFE /* CoreStore+Logging.swift in Sources */,
B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */,
B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B50E175023517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5215CA71FA47DFD00139E3A /* FetchChainBuilder.swift in Sources */,
B5D33A041E96012400C880DE /* Relationship.swift in Sources */,
@@ -2655,6 +2699,7 @@
B52F74401E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
18166886232B9ED20097C275 /* KeyPath+KeyPaths.swift in Sources */,
B5E8A72321C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
B50E175F2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5474D182227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
B501322E2346A9B100FC238B /* LiveList.swift in Sources */,
B56924021EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */,
@@ -2726,7 +2771,7 @@
B56321AD1BD6521C006C9394 /* Internals.MigrationManager.swift in Sources */,
B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */,
B56321961BD65216006C9394 /* From.swift in Sources */,
B5BF7FBE234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */,
B5BF7FBE234C99190070E741 /* Internals.DiffableDataUIDispatcher.swift in Sources */,
B5D339DA1E9489AB00C880DE /* CoreStoreObject.swift in Sources */,
B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */,
B56923FC1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */,
@@ -2745,6 +2790,7 @@
B5A992211EA898720091A2E3 /* UserInfo.swift in Sources */,
B563218C1BD65216006C9394 /* DataStack+Transaction.swift in Sources */,
B5D339E41E948C3600C880DE /* Value.swift in Sources */,
B50E175423517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
B53FBA0E1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift in Sources */,
B563219E1BD65216006C9394 /* CoreStore+Observing.swift in Sources */,
B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */,
@@ -2762,6 +2808,7 @@
B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5E1B5A01CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
B5ECDC261CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */,
B50E17632351FA66004F033C /* Internals.Closure.swift in Sources */,
B563217F1BD65216006C9394 /* CoreStore.swift in Sources */,
B549F6751E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
@@ -2834,11 +2881,13 @@
B56321A51BD65216006C9394 /* MigrationChain.swift in Sources */,
B5E222261CA4E12600BA2E95 /* CSSynchronousDataTransaction.swift in Sources */,
B56321A21BD65216006C9394 /* ListObserver.swift in Sources */,
B50E175923517DE4004F033C /* Differentiable.swift in Sources */,
B5215CAB1FA4810300139E3A /* QueryChainBuilder.swift in Sources */,
B563218A1BD65216006C9394 /* SynchronousDataTransaction.swift in Sources */,
B52F744C1E9B8740005F3DAC /* CoreStoreSchema.swift in Sources */,
B5AEFAB71C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
B5E2222D1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
B50E174F23517C03004F033C /* Internals.DiffableDataUIDispatcher.StagedChangeset.swift in Sources */,
B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */,
B56321B61BD6521C006C9394 /* Internals.WeakObject.swift in Sources */,
B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
@@ -2874,6 +2923,7 @@
B52F743F1E9B8724005F3DAC /* DynamicSchema.swift in Sources */,
18166885232B9ED10097C275 /* KeyPath+KeyPaths.swift in Sources */,
B5E8A72221C1015300EF006A /* CoreStoreObject+Observing.swift in Sources */,
B50E175E2351848E004F033C /* Internals.DiffableDataUIDispatcher.DiffResult.swift in Sources */,
B5474D172227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift in Sources */,
B501322D2346A9B000FC238B /* LiveList.swift in Sources */,
B56924011EB82976007C4DC9 /* CSUnsafeDataModelSchema.swift in Sources */,

View File

@@ -510,18 +510,18 @@
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
<rect key="frame" x="0.0" y="0.0" width="375" height="309"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<rect key="frame" x="16" y="69.5" width="343" height="77.5"/>
<rect key="frame" x="16" y="69.5" width="343" height="80"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa">
<rect key="frame" x="16" y="157" width="343" height="18"/>
<rect key="frame" x="16" y="159.5" width="343" height="18"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
</constraints>
@@ -530,37 +530,37 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
<rect key="frame" x="16" y="195" width="74" height="18"/>
<rect key="frame" x="16" y="197.5" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
<rect key="frame" x="16" y="233" width="74" height="18"/>
<rect key="frame" x="16" y="235.5" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
<rect key="frame" x="16" y="271" width="74" height="18"/>
<rect key="frame" x="16" y="273.5" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
<rect key="frame" x="98" y="189" width="263" height="31"/>
<rect key="frame" x="98" y="191.5" width="263" height="31"/>
<connections>
<action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/>
</connections>
</slider>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd">
<rect key="frame" x="98" y="227" width="263" height="31"/>
<rect key="frame" x="98" y="229.5" width="263" height="31"/>
<connections>
<action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/>
</connections>
</slider>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP">
<rect key="frame" x="98" y="265" width="263" height="31"/>
<rect key="frame" x="98" y="267.5" width="263" height="31"/>
<connections>
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
</connections>
@@ -637,34 +637,52 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
<rect key="frame" x="0.0" y="44" width="375" height="311.5"/>
<connections>
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
</connections>
</containerView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
<rect key="frame" x="0.0" y="355.5" width="375" height="311.5"/>
<connections>
<segue destination="bLr-DM-i2t" kind="embed" id="OdS-gZ-CUA"/>
</connections>
</containerView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="cce-yT-4dn">
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<connections>
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
</connections>
</containerView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
<rect key="frame" x="0.0" y="311.5" width="375" height="311.5"/>
<connections>
<segue destination="bLr-DM-i2t" kind="embed" id="OdS-gZ-CUA"/>
</connections>
</containerView>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="6So-f3-4Gp" firstAttribute="top" secondItem="L5f-tW-lXf" secondAttribute="bottom" id="3m8-tj-Nd4"/>
<constraint firstAttribute="trailing" secondItem="6So-f3-4Gp" secondAttribute="trailing" id="4L8-wZ-F59"/>
<constraint firstItem="L5f-tW-lXf" firstAttribute="height" secondItem="6So-f3-4Gp" secondAttribute="height" id="8XS-L3-hvN"/>
<constraint firstAttribute="bottom" secondItem="6So-f3-4Gp" secondAttribute="bottom" id="8wL-zm-wnt"/>
<constraint firstItem="L5f-tW-lXf" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="CbE-2f-7wk"/>
<constraint firstAttribute="trailing" secondItem="L5f-tW-lXf" secondAttribute="trailing" id="dso-2g-fgA"/>
<constraint firstItem="6So-f3-4Gp" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="eXM-D3-NLv"/>
<constraint firstItem="L5f-tW-lXf" firstAttribute="top" secondItem="IML-3o-caw" secondAttribute="bottom" id="zJ5-sE-iJA"/>
<constraint firstAttribute="trailing" secondItem="cce-yT-4dn" secondAttribute="trailing" id="DE1-u6-1mr"/>
<constraint firstItem="LNL-mj-D7l" firstAttribute="top" secondItem="cce-yT-4dn" secondAttribute="bottom" id="WIZ-M3-bdr"/>
<constraint firstItem="cce-yT-4dn" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="ZiS-j0-ANp"/>
<constraint firstItem="cce-yT-4dn" firstAttribute="top" secondItem="IML-3o-caw" secondAttribute="bottom" id="zjs-CM-7r5"/>
</constraints>
</view>
<extendedEdge key="edgesForExtendedLayout" top="YES"/>
<navigationItem key="navigationItem" title="Colors" id="7Gd-Ad-Bzu"/>
<navigationItem key="navigationItem" title="Colors" id="7Gd-Ad-Bzu">
<rightBarButtonItems>
<barButtonItem title="▼" width="36" id="aPM-OC-EB8">
<connections>
<action selector="toggleBottomContainerView" destination="YOI-b7-Nxn" id="7fn-z6-pne"/>
</connections>
</barButtonItem>
<barButtonItem title="▲" width="36" id="6q3-Zk-A9p">
<connections>
<action selector="toggleTopContainerView" destination="YOI-b7-Nxn" id="APN-Kk-C3n"/>
</connections>
</barButtonItem>
</rightBarButtonItems>
</navigationItem>
<nil key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="bottomContainerView" destination="6So-f3-4Gp" id="C7O-e2-k0l"/>
<outlet property="topContainerView" destination="L5f-tW-lXf" id="DRV-mm-rBY"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="C9h-Ba-WoL" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
@@ -700,7 +718,7 @@
<objects>
<tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
<rect key="frame" x="0.0" y="0.0" width="375" height="309"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
@@ -885,11 +903,11 @@
<viewControllerLayoutGuide type="bottom" id="Q1w-Zn-fPm"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="kof-sr-Xm1">
<rect key="frame" x="0.0" y="0.0" width="375" height="309"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="SwiftUI is not supported on this device/OS version" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="M6C-fv-74B">
<rect key="frame" x="16" y="124.5" width="343" height="60"/>
<rect key="frame" x="16" y="126" width="343" height="60"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="25"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>

View File

@@ -43,10 +43,10 @@ struct ColorsDemo {
didSet {
self.palettes.refetch(
self.filter.whereClause(),
OrderBy<Palette>(.ascending(\.hue))
)
// self.palettes.refetch(
// self.filter.whereClause(),
// OrderBy<Palette>(.ascending(\.hue))
// )
}
}
@@ -73,9 +73,9 @@ struct ColorsDemo {
return dataStack
}()
static let palettes: ListMonitor<Palette> = {
static let palettes: LiveList<Palette> = {
return ColorsDemo.stack.monitorSectionedList(
return ColorsDemo.stack.liveList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))
@@ -86,15 +86,7 @@ struct ColorsDemo {
// MARK: - ListObserverDemoViewController
class ListObserverDemoViewController: UITableViewController, ListSectionObserver {
// MARK: NSObject
deinit {
ColorsDemo.palettes.removeObserver(self)
}
class ListObserverDemoViewController: UITableViewController {
// MARK: UIViewController
@@ -132,10 +124,26 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
filterBarButton
]
self.filterBarButton = filterBarButton
ColorsDemo.palettes.addObserver(self)
self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch)
self.dataSource = DiffableDataSource.TableView<Palette>(
tableView: self.tableView,
dataStack: ColorsDemo.stack,
cellProvider: { (tableView, indexPath, palette) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
return cell
}
)
ColorsDemo.palettes.addObserver(self) { [weak self] (liveList, snapshot) in
guard let self = self else {
return
}
self.dataSource?.apply(snapshot, animatingDifferences: true)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -153,30 +161,6 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
}
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return ColorsDemo.palettes.numberOfSections()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ColorsDemo.palettes.numberOfObjects(in: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
let palette = ColorsDemo.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
return cell
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@@ -185,7 +169,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
self.performSegue(
withIdentifier: "ObjectObserverDemoViewController",
sender: ColorsDemo.palettes[indexPath]
sender: ColorsDemo.palettes[indexPath: indexPath]?.object
)
}
@@ -194,11 +178,11 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
switch editingStyle {
case .delete:
let palette = ColorsDemo.palettes[indexPath]
let palette = ColorsDemo.palettes[indexPath: indexPath]
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.delete(palette)
transaction.delete(palette?.object)
},
completion: { _ in }
)
@@ -208,81 +192,16 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return ColorsDemo.palettes.sectionInfo(at: section).name
}
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<Palette>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Palette>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(_ monitor: ListMonitor<Palette>) {
self.setTable(enabled: false)
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Palette>) {
self.filterBarButton?.title = ColorsDemo.filter.rawValue
self.tableView.reloadData()
self.setTable(enabled: true)
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: IndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: IndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) {
if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {
let palette = ColorsDemo.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
}
}
func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
self.tableView.moveRow(at: fromIndexPath, to: toIndexPath)
}
// MARK: ListSectionObserver
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
}
// MARK: Private
private var filterBarButton: UIBarButtonItem?
private var dataSource: DiffableDataSource.TableView<Palette>?
deinit {
ColorsDemo.palettes.removeObserver(self)
}
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
@@ -313,8 +232,6 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
}
@IBAction private dynamic func shuffleBarButtonItemTouched(_ sender: AnyObject?) {
self.setTable(enabled: false)
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
@@ -324,28 +241,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
palette.colorName .= nil
}
},
completion: { _ in
self.setTable(enabled: true)
}
)
}
private func setTable(enabled: Bool) {
tableView.isUserInteractionEnabled = enabled
UIView.animate(
withDuration: 0.2,
delay: 0,
options: .beginFromCurrentState,
animations: { () -> Void in
if let tableView = self.tableView {
tableView.alpha = enabled ? 1.0 : 0.5
}
},
completion: nil
completion: { _ in }
)
}
}

View File

@@ -27,4 +27,20 @@ class ObserversViewController: UIViewController {
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// MARK: Private
@IBOutlet private dynamic weak var topContainerView: UIView?
@IBOutlet private dynamic weak var bottomContainerView: UIView?
@IBAction private dynamic func toggleTopContainerView() {
self.topContainerView?.isHidden.toggle()
}
@IBAction private dynamic func toggleBottomContainerView() {
self.bottomContainerView?.isHidden.toggle()
}
}

View File

@@ -31,7 +31,6 @@ import CoreData
// MARK: - DataStack
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension DataStack {
public func liveList<D>(_ from: From<D>, _ fetchClauses: FetchClause...) -> LiveList<D> {

View File

@@ -29,79 +29,324 @@ import UIKit
import CoreData
//// MARK: - DiffableDataSource
//
//extension DiffableDataSource {
//
// // MARK: - TableView
//
// public open class TableView<D: DynamicObject>: NSObject, UITableViewDataSource {
//
// // MARK: Public
//
// public typealias ObjectType = D
//
// public var defaultRowAnimation: UITableView.RowAnimation = .automatic
//
// public init(tableView: UITableView, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) {
//
// self.tableView = tableView
// self.cellProvider = cellProvider
//
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - TableView
open class TableView<O: DynamicObject>: NSObject, UITableViewDataSource {
// MARK: Open
@nonobjc
open var defaultRowAnimation: UITableView.RowAnimation = .automatic
// MARK: Public
public typealias ObjectType = O
@nonobjc
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, ObjectType) -> UITableViewCell?) {
self.tableView = tableView
self.cellProvider = cellProvider
self.dataStack = dataStack
super.init()
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// self.rawDataSource = UITableViewDiffableDataSource<String, D.ObjectID>(
// self.rawDataSource = UITableViewDiffableDataSource<String, O.ObjectID>(
// tableView: tableView,
// cellProvider: { (tableView, indexPath, managedObjectID) -> UITableViewCell? in
// cellProvider: { [weak self] (tableView, indexPath, objectID) -> UITableViewCell? in
//
// cellProvider(
// guard let self = self else {
//
// return nil
// }
// guard let object = self.dataStack.fetchExisting(objectID) as O? else {
//
// return nil
// }
// return self.cellProvider(tableView, indexPath, object)
// }
// )
// }
// else {
//
// self.rawDataSource = nil
self.rawDataSource = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
// }
//
// super.init()
//
// tableView.dataSource = self
// }
//
// public func apply(_ snapshot: ListSnapshot<ObjectType>, animatingDifferences: Bool = true) {
//
// let dataSource = UITableViewDiffableDataSource<String, D>.
tableView.dataSource = self
}
public func apply(_ snapshot: ListSnapshot<ObjectType>, animatingDifferences: Bool = true) {
let diffableSnapshot = snapshot.diffableSnapshot
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// self.rawDataSource! as! UITableViewDiffableDataSource<String, D>
//
// self.modernDataSource.apply(
// diffableSnapshot as! NSDiffableDataSourceSnapshot<String, NSManagedObjectID>,
// animatingDifferences: animatingDifferences,
// completion: nil
// )
// }
// else {
//
self.legacyDataSource.apply(
diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
view: self.tableView,
animatingDifferences: animatingDifferences,
performUpdates: { tableView, changeset, setSections in
tableView.reload(
using: changeset,
with: self.defaultRowAnimation,
setData: setSections
)
}
)
// }
// core.apply(
// snapshot,
// view: tableView,
// animatingDifferences: animatingDifferences,
// performUpdates: { tableView, changeset, setSections in
// tableView.reload(using: changeset, with: self.defaultRowAnimation, setData: setSections)
// })
// }
}
public func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// return self.modernDataSource.itemIdentifier(for: indexPath)
// }
// else {
return self.legacyDataSource.itemIdentifier(for: indexPath)
// }
}
public func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// // MARK: Private
// return self.modernDataSource.indexPath(for: itemIdentifier)
// }
// else {
return self.legacyDataSource.indexPath(for: itemIdentifier)
// }
}
// MARK: - UITableViewDataSource
@objc
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// private weak var tableView: UITableView?
// private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell?
// private let rawDataSource: Any?
// return self.modernDataSource.numberOfSections(in: tableView)
// }
// else {
return self.legacyDataSource.numberOfSections()
// }
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// return self.modernDataSource.tableView(tableView, numberOfRowsInSection: section)
// }
// else {
return self.legacyDataSource.numberOfItems(inSection: section)
// }
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// return self.modernDataSource.snapshot().sectionIdentifiers[section]
// }
// else {
return self.legacyDataSource.sectionIdentifier(inSection: section)
// }
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// return self.modernDataSource.tableView(tableView, titleForFooterInSection: section)
// }
// else {
return nil
// }
}
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// return self.modernDataSource.tableView(tableView, cellForRowAt: indexPath)
// }
// else {
guard let objectID = self.legacyDataSource.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
// }
}
// MARK: Private
private weak var tableView: UITableView?
private let dataStack: DataStack
private let cellProvider: (UITableView, IndexPath, ObjectType) -> UITableViewCell?
private var rawDataSource: Any!
// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
// private var diffableDataSource: UITableViewDiffableDataSource<String, D.ObjectID> {
// private var modernDataSource: UITableViewDiffableDataSource<String, O.ObjectID> {
//
// return self.rawDataSource! as! UITableViewDiffableDataSource<String, D.ObjectID>
// return self.rawDataSource as! UITableViewDiffableDataSource<String, O.ObjectID>
// }
// }
//}
private var legacyDataSource: Internals.DiffableDataUIDispatcher<O> {
return self.rawDataSource as! Internals.DiffableDataUIDispatcher<O>
}
}
}
// MARK: - UITableView
extension UITableView {
// MARK: FilePrivate
@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
)
}
@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,62 @@
//
// Differentiable.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: - Differentiable
@usableFromInline
internal protocol Differentiable {
associatedtype DifferenceIdentifier: Hashable
var differenceIdentifier: DifferenceIdentifier { get }
func isContentEqual(to source: Self) -> Bool
}
extension Differentiable where Self: AnyObject {
// MARK: Internal
internal var differenceIdentifier: ObjectIdentifier {
return .init(self)
}
}
// MARK: - DifferentiableSection
@usableFromInline
internal protocol DifferentiableSection: Differentiable {
associatedtype Collection: Swift.Collection where Collection.Element: Differentiable
var elements: Collection { get }
init<S: Sequence>(source: Self, elements: S) where S.Element == Collection.Element
}

View File

@@ -0,0 +1,54 @@
//
// Internals.Closure.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: - Internals
extension Internals {
// MARK: - Closure
internal final class Closure<T, U> {
// MARK: FilePrivate
internal init(_ closure: @escaping (T) -> U) {
self.closure = closure
}
internal func invoke(with argument: T) -> U {
return self.closure(argument)
}
// MARK: Private
private let closure: (T) -> U
}
}

View File

@@ -51,6 +51,18 @@ extension Internals {
self.structure = .init(sections: sections)
}
var sections: [Section] {
get {
return self.structure.sections
}
set {
self.structure.sections = newValue
}
}
// MARK: DiffableDataSourceSnapshotProtocol
@@ -186,48 +198,66 @@ extension Internals {
private var structure: BackingStructure
// MARK: - ItemStateID
// MARK: - Section
internal struct ItemStateID: Identifiable, Equatable {
internal struct Section: DifferentiableSection, Equatable {
let stateTag: UUID
var isReloaded: Bool
init(id: NSManagedObjectID, stateTag: UUID) {
self.id = id
self.stateTag = stateTag
init(differenceIdentifier: String, items: [Item] = [], isReloaded: Bool = false) {
self.differenceIdentifier = differenceIdentifier
self.elements = items
self.isReloaded = isReloaded
}
func isContentEqual(to source: ItemStateID) -> Bool {
// MARK: Differentiable
return self.id == source.id && self.stateTag == source.stateTag
let differenceIdentifier: String
func isContentEqual(to source: Section) -> Bool {
return !self.isReloaded
&& self.differenceIdentifier == source.differenceIdentifier
}
// MARK: DifferentiableSection
var elements: [Item] = []
// MARK: Identifiable
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
let id: NSManagedObjectID
self.init(
differenceIdentifier: source.differenceIdentifier,
items: Array(elements),
isReloaded: source.isReloaded
)
}
}
// MARK: - SectionStateID
// MARK: - Item
internal struct SectionStateID: Identifiable, Equatable {
internal struct Item: Differentiable, Equatable {
let stateTag: UUID
var isReloaded: Bool
init(id: String, stateTag: UUID) {
self.id = id
self.stateTag = stateTag
init(differenceIdentifier: NSManagedObjectID, isReloaded: Bool = false) {
self.differenceIdentifier = differenceIdentifier
self.isReloaded = isReloaded
}
func isContentEqual(to source: SectionStateID) -> Bool {
// MARK: Differentiable
return self.id == source.id && self.stateTag == source.stateTag
let differenceIdentifier: NSManagedObjectID
func isContentEqual(to source: Item) -> Bool {
return !self.isReloaded
&& self.differenceIdentifier == source.differenceIdentifier
}
// MARK: Identifiable
let id: String
}
@@ -249,22 +279,22 @@ extension Internals {
self.sections = sections.map {
Section(
id: $0.name,
differenceIdentifier: $0.name,
items: $0.objects?
.compactMap({ ($0 as? NSManagedObject)?.objectID })
.map({ Item(id: $0) }) ?? []
.map({ Item(differenceIdentifier: $0) }) ?? []
)
}
}
var allSectionIDs: [String] {
return self.sections.map({ $0.id })
return self.sections.map({ $0.differenceIdentifier })
}
var allItemIDs: [NSManagedObjectID] {
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.id })
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.differenceIdentifier })
}
func items(in sectionID: String) -> [NSManagedObjectID] {
@@ -273,12 +303,12 @@ extension Internals {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
return self.sections[sectionIndex].elements.map({ $0.id })
return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier })
}
func section(containing itemID: NSManagedObjectID) -> String? {
return self.itemPositionMap()[itemID]?.section.id
return self.itemPositionMap()[itemID]?.section.differenceIdentifier
}
mutating func append(itemIDs: [NSManagedObjectID], to sectionID: String?) {
@@ -301,7 +331,7 @@ extension Internals {
}
index = section.index(before: section.endIndex)
}
let items = itemIDs.lazy.map({ Item(id: $0) })
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[index].elements.append(contentsOf: items)
}
@@ -311,7 +341,7 @@ extension Internals {
Internals.abort("Item \(beforeItemID) does not exist")
}
let items = itemIDs.lazy.map({ Item(id: $0) })
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
}
@@ -324,7 +354,7 @@ extension Internals {
}
let itemIndex = self.sections[itemPosition.sectionIndex].elements
.index(after: itemPosition.itemRelativeIndex)
let items = itemIDs.lazy.map({ Item(id: $0) })
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemIndex)
}
@@ -406,7 +436,7 @@ extension Internals {
mutating func append(sectionIDs: [String]) {
let newSections = sectionIDs.lazy.map({ Section(id: $0) })
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.append(contentsOf: newSections)
}
@@ -416,7 +446,7 @@ extension Internals {
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
}
let newSections = sectionIDs.lazy.map({ Section(id: $0) })
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
@@ -427,7 +457,7 @@ extension Internals {
Internals.abort("Section \"\(afterSectionID)\" does not exist")
}
let sectionIndex = self.sections.index(after: beforeIndex)
let newSections = sectionIDs.lazy.map({ Section(id: $0) })
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
@@ -481,11 +511,9 @@ extension Internals {
// MARK: Private
private static let zeroUUID: UUID = .init(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
private func sectionIndex(of sectionID: String) -> Array<Section>.Index? {
return self.sections.firstIndex(where: { $0.id == sectionID })
return self.sections.firstIndex(where: { $0.differenceIdentifier == sectionID })
}
@discardableResult
@@ -515,7 +543,7 @@ extension Internals {
for (itemRelativeIndex, item) in section.element.elements.enumerated() {
result[item.id] = ItemPosition(
result[item.differenceIdentifier] = ItemPosition(
item: item,
itemRelativeIndex: itemRelativeIndex,
section: section.element,
@@ -526,58 +554,6 @@ extension Internals {
}
// MARK: - Item
fileprivate struct Item: Identifiable, Equatable {
var isReloaded: Bool
init(id: NSManagedObjectID, isReloaded: Bool = false) {
self.id = id
self.isReloaded = isReloaded
}
func isContentEqual(to source: Item) -> Bool {
return !self.isReloaded && self.id == source.id
}
// MARK: Identifiable
let id: NSManagedObjectID
}
// MARK: - Section
fileprivate struct Section: Identifiable, Equatable {
var elements: [Item] = []
var isReloaded: Bool
init(id: String, items: [Item] = [], isReloaded: Bool = false) {
self.id = id
self.elements = items
self.isReloaded = isReloaded
}
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
self.init(id: source.id, items: Array(elements), isReloaded: source.isReloaded)
}
func isContentEqual(to source: Section) -> Bool {
return !self.isReloaded && self.id == source.id
}
// MARK: Identifiable
let id: String
}
// MARK: - ItemPosition
fileprivate struct ItemPosition {
@@ -595,73 +571,7 @@ extension Internals {
// MARK: - NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol where SectionIdentifierType == NSString, ItemIdentifierType == NSManagedObjectID {
internal var sectionIdentifiers: [String] {
return self.sectionIdentifiers as [NSString] as [String]
}
internal func numberOfItems(inSection identifier: String) -> Int {
return self.numberOfItems(inSection: identifier as NSString)
}
internal func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID] {
return self.itemIdentifiers(inSection: identifier as NSString)
}
internal func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String? {
return self.sectionIdentifier(containingItem: identifier) as NSString? as String?
}
internal func indexOfSection(_ identifier: String) -> Int? {
return self.indexOfSection(identifier as NSString)
}
internal mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?) {
self.appendItems(identifiers, toSection: sectionIdentifier as NSString?)
}
internal mutating func appendSections(_ identifiers: [String]) {
self.appendSections(identifiers as [NSString])
}
internal mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String) {
self.insertSections(identifiers as [NSString], beforeSection: toIdentifier as NSString)
}
internal mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String) {
return self.insertSections(identifiers as [NSString], afterSection: toIdentifier as NSString)
}
internal mutating func deleteSections(_ identifiers: [String]) {
self.deleteSections(identifiers as [NSString])
}
internal mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) {
self.moveSection(identifier as NSString, beforeSection: toIdentifier as NSString)
}
internal mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) {
self.moveSection(identifier as NSString, afterSection: toIdentifier as NSString)
}
internal mutating func reloadSections(_ identifiers: [String]) {
self.reloadSections(identifiers as [NSString])
}
}
extension NSDiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol where SectionIdentifierType == String, ItemIdentifierType == NSManagedObjectID {}
#endif

View File

@@ -0,0 +1,141 @@
//
// Internals.DiffableDataUIDispatcher.Changeset.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
// MARK: - Internals.DiffableDataUIDispatcher
extension Internals.DiffableDataUIDispatcher {
// MARK: - ChangeSet
internal struct Changeset<C: Collection>: Equatable where C: Equatable {
var data: C
var sectionDeleted: [Int]
var sectionInserted: [Int]
var sectionUpdated: [Int]
var sectionMoved: [(source: Int, target: Int)]
var elementDeleted: [ElementPath]
var elementInserted: [ElementPath]
var elementUpdated: [ElementPath]
var elementMoved: [(source: ElementPath, target: ElementPath)]
@inlinable
init(
data: C,
sectionDeleted: [Int] = [],
sectionInserted: [Int] = [],
sectionUpdated: [Int] = [],
sectionMoved: [(source: Int, target: Int)] = [],
elementDeleted: [ElementPath] = [],
elementInserted: [ElementPath] = [],
elementUpdated: [ElementPath] = [],
elementMoved: [(source: ElementPath, target: ElementPath)] = []
) {
self.data = data
self.sectionDeleted = sectionDeleted
self.sectionInserted = sectionInserted
self.sectionUpdated = sectionUpdated
self.sectionMoved = sectionMoved
self.elementDeleted = elementDeleted
self.elementInserted = elementInserted
self.elementUpdated = elementUpdated
self.elementMoved = elementMoved
}
@inlinable
var sectionChangeCount: Int {
return self.sectionDeleted.count
+ self.sectionInserted.count
+ self.sectionUpdated.count
+ self.sectionMoved.count
}
@inlinable
var elementChangeCount: Int {
return self.elementDeleted.count
+ self.elementInserted.count
+ self.elementUpdated.count
+ self.elementMoved.count
}
@inlinable
var changeCount: Int {
return self.sectionChangeCount + self.elementChangeCount
}
@inlinable
var hasSectionChanges: Bool {
return self.sectionChangeCount > 0
}
@inlinable
var hasElementChanges: Bool {
return self.elementChangeCount > 0
}
@inlinable
var hasChanges: Bool {
return self.changeCount > 0
}
// MARK: Equatable
static func == (lhs: Changeset, rhs: Changeset) -> Bool {
return lhs.data == rhs.data
&& Set(lhs.sectionDeleted) == Set(rhs.sectionDeleted)
&& Set(lhs.sectionInserted) == Set(rhs.sectionInserted)
&& Set(lhs.sectionUpdated) == Set(rhs.sectionUpdated)
&& Set(lhs.sectionMoved.map(HashablePair.init)) == Set(rhs.sectionMoved.map(HashablePair.init))
&& Set(lhs.elementDeleted) == Set(rhs.elementDeleted)
&& Set(lhs.elementInserted) == Set(rhs.elementInserted)
&& Set(lhs.elementUpdated) == Set(rhs.elementUpdated)
&& Set(lhs.elementMoved.map(HashablePair.init)) == Set(rhs.elementMoved.map(HashablePair.init))
}
// MARK: - HashablePair
private struct HashablePair<H: Hashable>: Hashable {
let first: H
let second: H
}
}
}
#endif

View File

@@ -0,0 +1,320 @@
//
// Internals.DiffableDataUIDispatcher.DiffResult.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
// MARK: - Internals.DiffableDataUIDispatcher
extension Internals.DiffableDataUIDispatcher {
// MARK: - DiffResult
@usableFromInline
internal struct DiffResult<Index> {
@usableFromInline
internal let deleted: [Index]
@usableFromInline
internal let inserted: [Index]
@usableFromInline
internal let updated: [Index]
@usableFromInline
internal let moved: [(source: Index, target: Index)]
@usableFromInline
internal let sourceTraces: ContiguousArray<Trace<Int>>
@usableFromInline
internal let targetReferences: ContiguousArray<Int?>
@inlinable
@discardableResult
static func diff<E: Differentiable>(
source: ContiguousArray<E>,
target: ContiguousArray<E>,
useTargetIndexForUpdated: Bool,
mapIndex: (Int) -> Index,
updatedElementsPointer: UnsafeMutablePointer<ContiguousArray<E>>? = nil,
notDeletedElementsPointer: UnsafeMutablePointer<ContiguousArray<E>>? = nil
) -> DiffResult<Index> {
var deleted = [Index]()
var inserted = [Index]()
var updated = [Index]()
var moved = [(source: Index, target: Index)]()
var sourceTraces = ContiguousArray<Trace<Int>>()
var sourceIdentifiers = ContiguousArray<E.DifferenceIdentifier>()
var targetReferences = ContiguousArray<Int?>(repeating: nil, count: target.count)
sourceTraces.reserveCapacity(source.count)
sourceIdentifiers.reserveCapacity(source.count)
for sourceElement in source {
sourceTraces.append(Trace())
sourceIdentifiers.append(sourceElement.differenceIdentifier)
}
sourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
var sourceOccurrencesTable = [TableKey<E.DifferenceIdentifier>: Occurrence](minimumCapacity: source.count)
for sourceIndex in sourceIdentifiers.indices {
let pointer = bufferPointer.baseAddress!.advanced(by: sourceIndex)
let key = TableKey(pointer: pointer)
switch sourceOccurrencesTable[key] {
case .none:
sourceOccurrencesTable[key] = .unique(index: sourceIndex)
case .unique(let otherIndex)?:
let reference = IndicesReference([otherIndex, sourceIndex])
sourceOccurrencesTable[key] = .duplicate(reference: reference)
case .duplicate(let reference)?:
reference.push(sourceIndex)
}
}
for targetIndex in target.indices {
var targetIdentifier = target[targetIndex].differenceIdentifier
let key = TableKey(pointer: &targetIdentifier)
switch sourceOccurrencesTable[key] {
case .none:
break
case .unique(let sourceIndex)?:
if case .none = sourceTraces[sourceIndex].reference {
targetReferences[targetIndex] = sourceIndex
sourceTraces[sourceIndex].reference = targetIndex
}
case .duplicate(let reference)?:
if let sourceIndex = reference.next() {
targetReferences[targetIndex] = sourceIndex
sourceTraces[sourceIndex].reference = targetIndex
}
}
}
}
var offsetByDelete = 0
var untrackedSourceIndex: Int? = 0
for sourceIndex in source.indices {
sourceTraces[sourceIndex].deleteOffset = offsetByDelete
if let targetIndex = sourceTraces[sourceIndex].reference {
let targetElement = target[targetIndex]
updatedElementsPointer?.pointee.append(targetElement)
notDeletedElementsPointer?.pointee.append(targetElement)
}
else {
let sourceElement = source[sourceIndex]
deleted.append(mapIndex(sourceIndex))
sourceTraces[sourceIndex].isTracked = true
offsetByDelete += 1
updatedElementsPointer?.pointee.append(sourceElement)
}
}
for targetIndex in target.indices {
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
sourceTraces.suffix(from: index).firstIndex { !$0.isTracked }
}
if let sourceIndex = targetReferences[targetIndex] {
sourceTraces[sourceIndex].isTracked = true
let sourceElement = source[sourceIndex]
let targetElement = target[targetIndex]
if !targetElement.isContentEqual(to: sourceElement) {
updated.append(mapIndex(useTargetIndexForUpdated ? targetIndex : sourceIndex))
}
if sourceIndex != untrackedSourceIndex {
let deleteOffset = sourceTraces[sourceIndex].deleteOffset
moved.append((source: mapIndex(sourceIndex - deleteOffset), target: mapIndex(targetIndex)))
}
}
else {
inserted.append(mapIndex(targetIndex))
}
}
return DiffResult(
deleted: deleted,
inserted: inserted,
updated: updated,
moved: moved,
sourceTraces: sourceTraces,
targetReferences: targetReferences
)
}
// MARK: Private
@inlinable
internal init(
deleted: [Index] = [],
inserted: [Index] = [],
updated: [Index] = [],
moved: [(source: Index, target: Index)] = [],
sourceTraces: ContiguousArray<Trace<Int>>,
targetReferences: ContiguousArray<Int?>
) {
self.deleted = deleted
self.inserted = inserted
self.updated = updated
self.moved = moved
self.sourceTraces = sourceTraces
self.targetReferences = targetReferences
}
// MARK: - Trace
@usableFromInline
internal struct Trace<Index> {
@usableFromInline
internal var reference: Index?
@usableFromInline
internal var deleteOffset = 0
@usableFromInline
internal var isTracked = false
@inlinable
init() {}
}
// MARK: - Occurrence
@usableFromInline
internal enum Occurrence {
case unique(index: Int)
case duplicate(reference: IndicesReference)
}
// MARK: - IndicesReference
@usableFromInline
internal final class IndicesReference {
@usableFromInline
internal var indices: ContiguousArray<Int>
@usableFromInline
internal var position = 0
@inlinable
internal init(_ indices: ContiguousArray<Int>) {
self.indices = indices
}
@inlinable
internal func push(_ index: Int) {
self.indices.append(index)
}
@inlinable
internal func next() -> Int? {
guard self.position < self.indices.endIndex else {
return nil
}
defer {
self.position += 1
}
return self.indices[self.position]
}
}
// MARK: - TableKey
@usableFromInline
internal struct TableKey<T: Hashable>: Hashable {
@usableFromInline
internal let pointeeHashValue: Int
@usableFromInline
internal let pointer: UnsafePointer<T>
@inlinable
internal init(pointer: UnsafePointer<T>) {
self.pointeeHashValue = pointer.pointee.hashValue
self.pointer = pointer
}
// MARK: Equatable
@inlinable
internal static func == (lhs: TableKey, rhs: TableKey) -> Bool {
return lhs.pointeeHashValue == rhs.pointeeHashValue
&& (lhs.pointer.distance(to: rhs.pointer) == 0
|| lhs.pointer.pointee == rhs.pointer.pointee)
}
// MARK: Hashable
@inlinable
internal func hash(into hasher: inout Hasher) {
hasher.combine(pointeeHashValue)
}
}
}
}
#endif

View File

@@ -0,0 +1,516 @@
//
// Internals.DiffableDataUIDispatcher.StagedChangeset.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
// MARK: - Internals.DiffableDataUIDispatcher
extension Internals.DiffableDataUIDispatcher {
// MARK: - StagedChangeset
internal struct StagedChangeset<C: Collection>: ExpressibleByArrayLiteral, Equatable, RandomAccessCollection, RangeReplaceableCollection where C: Equatable {
@usableFromInline
var changesets: ContiguousArray<Changeset<C>>
@inlinable
init<S: Sequence>(_ changesets: S) where S.Element == Changeset<C> {
self.changesets = ContiguousArray(changesets)
}
// MARK: ExpressibleByArrayLiteral
@inlinable
init(arrayLiteral elements: Changeset<C>...) {
self.init(elements)
}
// MARK: Equatable
@inlinable
static func == (lhs: StagedChangeset, rhs: StagedChangeset) -> Bool {
return lhs.changesets == rhs.changesets
}
// MARK: Sequence
typealias Element = Changeset<C>
// MARK: RandomAccessCollection
@inlinable
var startIndex: Int {
return self.changesets.startIndex
}
@inlinable
var endIndex: Int {
return self.changesets.endIndex
}
@inlinable
func index(after i: Int) -> Int {
return self.changesets.index(after: i)
}
@inlinable
subscript(position: Int) -> Changeset<C> {
get { return self.changesets[position] }
set { self.changesets[position] = newValue }
}
// MARK: RangeReplaceableCollection
@inlinable
init() {
self.init([])
}
@inlinable
mutating func replaceSubrange<C2: Collection, R: RangeExpression>(_ subrange: R, with newElements: C2) where C2.Element == Changeset<C>, R.Bound == Int {
self.changesets.replaceSubrange(subrange, with: newElements)
}
}
}
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable {
@inlinable
internal init(source: C, target: C) {
self.init(source: source, target: target, section: 0)
}
@inlinable
internal init(source: C, target: C, section: Int) {
typealias Changeset = Internals.DiffableDataUIDispatcher<O>.Changeset
typealias ElementPath = Internals.DiffableDataUIDispatcher<O>.ElementPath
typealias DiffResult = Internals.DiffableDataUIDispatcher<O>.DiffResult
let sourceElements = ContiguousArray(source)
let targetElements = ContiguousArray(target)
if sourceElements.isEmpty && targetElements.isEmpty {
self.init()
return
}
if !sourceElements.isEmpty && targetElements.isEmpty {
self.init(
[
Changeset(
data: target,
elementDeleted: sourceElements.indices.map {
ElementPath(
element: $0,
section: section
)
}
)
]
)
return
}
if sourceElements.isEmpty && !targetElements.isEmpty {
self.init(
[
Changeset(
data: target,
elementInserted: targetElements.indices.map {
ElementPath(
element: $0,
section: section
)
}
)
]
)
return
}
var firstStageElements = ContiguousArray<C.Element>()
var secondStageElements = ContiguousArray<C.Element>()
let result = DiffResult.diff(
source: sourceElements,
target: targetElements,
useTargetIndexForUpdated: false,
mapIndex: { ElementPath(element: $0, section: section) },
updatedElementsPointer: &firstStageElements,
notDeletedElementsPointer: &secondStageElements
)
var changesets = ContiguousArray<Changeset<C>>()
if !result.updated.isEmpty {
changesets.append(
Changeset(
data: C(firstStageElements),
elementUpdated: result.updated
)
)
}
if !result.deleted.isEmpty {
changesets.append(
Changeset(
data: C(secondStageElements),
elementDeleted: result.deleted
)
)
}
if !result.inserted.isEmpty || !result.moved.isEmpty {
changesets.append(
Changeset(
data: target,
elementInserted: result.inserted,
elementMoved: result.moved
)
)
}
if !changesets.isEmpty {
let index = changesets.index(before: changesets.endIndex)
changesets[index].data = target
}
self.init(changesets)
}
}
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection {
@inlinable
internal init(source: C, target: C) {
typealias Section = C.Element
typealias SectionIdentifier = C.Element.DifferenceIdentifier
typealias Element = C.Element.Collection.Element
typealias ElementIdentifier = C.Element.Collection.Element.DifferenceIdentifier
typealias Changeset = Internals.DiffableDataUIDispatcher<O>.Changeset
typealias ElementPath = Internals.DiffableDataUIDispatcher<O>.ElementPath
typealias DiffResult = Internals.DiffableDataUIDispatcher<O>.DiffResult
typealias Trace = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.Trace
typealias TableKey = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.TableKey
typealias Occurrence = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.Occurrence
typealias IndicesReference = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.IndicesReference
let sourceSections = ContiguousArray(source)
let targetSections = ContiguousArray(target)
let contiguousSourceSections = ContiguousArray(sourceSections.map { ContiguousArray($0.elements) })
let contiguousTargetSections = ContiguousArray(targetSections.map { ContiguousArray($0.elements) })
var firstStageSections = sourceSections
var secondStageSections = ContiguousArray<Section>()
var thirdStageSections = ContiguousArray<Section>()
var fourthStageSections = ContiguousArray<Section>()
var sourceElementTraces = contiguousSourceSections.map { section in
ContiguousArray(repeating: Trace<ElementPath>(), count: section.count)
}
var targetElementReferences = contiguousTargetSections.map { section in
ContiguousArray<ElementPath?>(repeating: nil, count: section.count)
}
let flattenSourceCount = contiguousSourceSections.reduce(into: 0) { $0 += $1.count }
var flattenSourceIdentifiers = ContiguousArray<ElementIdentifier>()
var flattenSourceElementPaths = ContiguousArray<ElementPath>()
thirdStageSections.reserveCapacity(contiguousTargetSections.count)
fourthStageSections.reserveCapacity(contiguousTargetSections.count)
flattenSourceIdentifiers.reserveCapacity(flattenSourceCount)
flattenSourceElementPaths.reserveCapacity(flattenSourceCount)
let sectionResult = DiffResult.diff(
source: sourceSections,
target: targetSections,
useTargetIndexForUpdated: true,
mapIndex: { $0 }
)
var elementDeleted = [ElementPath]()
var elementInserted = [ElementPath]()
var elementUpdated = [ElementPath]()
var elementMoved = [(source: ElementPath, target: ElementPath)]()
for sourceSectionIndex in contiguousSourceSections.indices {
for sourceElementIndex in contiguousSourceSections[sourceSectionIndex].indices {
let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex)
let sourceElement = contiguousSourceSections[sourceElementPath]
flattenSourceIdentifiers.append(sourceElement.differenceIdentifier)
flattenSourceElementPaths.append(sourceElementPath)
}
}
flattenSourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
var sourceOccurrencesTable = [TableKey<ElementIdentifier>: Occurrence](minimumCapacity: flattenSourceCount)
for flattenSourceIndex in flattenSourceIdentifiers.indices {
let pointer = bufferPointer.baseAddress!.advanced(by: flattenSourceIndex)
let key = TableKey(pointer: pointer)
switch sourceOccurrencesTable[key] {
case .none:
sourceOccurrencesTable[key] = .unique(index: flattenSourceIndex)
case .unique(let otherIndex)?:
let reference = IndicesReference([otherIndex, flattenSourceIndex])
sourceOccurrencesTable[key] = .duplicate(reference: reference)
case .duplicate(let reference)?:
reference.push(flattenSourceIndex)
}
}
for targetSectionIndex in contiguousTargetSections.indices {
let targetElements = contiguousTargetSections[targetSectionIndex]
for targetElementIndex in targetElements.indices {
var targetIdentifier = targetElements[targetElementIndex].differenceIdentifier
let key = TableKey(pointer: &targetIdentifier)
switch sourceOccurrencesTable[key] {
case .none:
break
case .unique(let flattenSourceIndex)?:
let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex]
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
if case .none = sourceElementTraces[sourceElementPath].reference {
targetElementReferences[targetElementPath] = sourceElementPath
sourceElementTraces[sourceElementPath].reference = targetElementPath
}
case .duplicate(let reference)?:
if let flattenSourceIndex = reference.next() {
let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex]
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
targetElementReferences[targetElementPath] = sourceElementPath
sourceElementTraces[sourceElementPath].reference = targetElementPath
}
}
}
}
}
for sourceSectionIndex in contiguousSourceSections.indices {
let sourceSection = sourceSections[sourceSectionIndex]
let sourceElements = contiguousSourceSections[sourceSectionIndex]
var firstStageElements = sourceElements
if case .some = sectionResult.sourceTraces[sourceSectionIndex].reference {
var offsetByDelete = 0
var secondStageElements = ContiguousArray<Element>()
for sourceElementIndex in sourceElements.indices {
let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex)
sourceElementTraces[sourceElementPath].deleteOffset = offsetByDelete
if let targetElementPath = sourceElementTraces[sourceElementPath].reference,
case .some = sectionResult.targetReferences[targetElementPath.section] {
let targetElement = contiguousTargetSections[targetElementPath]
firstStageElements[sourceElementIndex] = targetElement
secondStageElements.append(targetElement)
continue
}
elementDeleted.append(sourceElementPath)
sourceElementTraces[sourceElementPath].isTracked = true
offsetByDelete += 1
}
let secondStageSection = Section(source: sourceSection, elements: secondStageElements)
secondStageSections.append(secondStageSection)
}
let firstStageSection = Section(source: sourceSection, elements: firstStageElements)
firstStageSections[sourceSectionIndex] = firstStageSection
}
for targetSectionIndex in contiguousTargetSections.indices {
guard let sourceSectionIndex = sectionResult.targetReferences[targetSectionIndex] else {
thirdStageSections.append(targetSections[targetSectionIndex])
fourthStageSections.append(targetSections[targetSectionIndex])
continue
}
var untrackedSourceIndex: Int? = 0
let targetElements = contiguousTargetSections[targetSectionIndex]
let sectionDeleteOffset = sectionResult.sourceTraces[sourceSectionIndex].deleteOffset
let thirdStageSection = secondStageSections[sourceSectionIndex - sectionDeleteOffset]
thirdStageSections.append(thirdStageSection)
var fourthStageElements = ContiguousArray<Element>()
fourthStageElements.reserveCapacity(targetElements.count)
for targetElementIndex in targetElements.indices {
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
sourceElementTraces[sourceSectionIndex].suffix(from: index).firstIndex { !$0.isTracked }
}
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
let targetElement = contiguousTargetSections[targetElementPath]
guard
let sourceElementPath = targetElementReferences[targetElementPath],
let movedSourceSectionIndex = sectionResult.sourceTraces[sourceElementPath.section].reference
else {
fourthStageElements.append(targetElement)
elementInserted.append(targetElementPath)
continue
}
sourceElementTraces[sourceElementPath].isTracked = true
let sourceElement = contiguousSourceSections[sourceElementPath]
fourthStageElements.append(targetElement)
if !targetElement.isContentEqual(to: sourceElement) {
elementUpdated.append(sourceElementPath)
}
if sourceElementPath.section != sourceSectionIndex || sourceElementPath.element != untrackedSourceIndex {
let deleteOffset = sourceElementTraces[sourceElementPath].deleteOffset
let moveSourceElementPath = ElementPath(element: sourceElementPath.element - deleteOffset, section: movedSourceSectionIndex)
elementMoved.append((source: moveSourceElementPath, target: targetElementPath))
}
}
let fourthStageSection = Section(source: thirdStageSection, elements: fourthStageElements)
fourthStageSections.append(fourthStageSection)
}
var changesets = ContiguousArray<Changeset<C>>()
if !elementUpdated.isEmpty {
changesets.append(
Changeset(
data: C(firstStageSections),
elementUpdated: elementUpdated
)
)
}
if !sectionResult.deleted.isEmpty || !elementDeleted.isEmpty {
changesets.append(
Changeset(
data: C(secondStageSections),
sectionDeleted: sectionResult.deleted,
elementDeleted: elementDeleted
)
)
}
if !sectionResult.inserted.isEmpty || !sectionResult.moved.isEmpty {
changesets.append(
Changeset(
data: C(thirdStageSections),
sectionInserted: sectionResult.inserted,
sectionMoved: sectionResult.moved
)
)
}
if !elementInserted.isEmpty || !elementMoved.isEmpty {
changesets.append(
Changeset(
data: C(fourthStageSections),
elementInserted: elementInserted,
elementMoved: elementMoved
)
)
}
if !sectionResult.updated.isEmpty {
changesets.append(
Changeset(
data: target,
sectionUpdated: sectionResult.updated
)
)
}
if !changesets.isEmpty {
let index = changesets.index(before: changesets.endIndex)
changesets[index].data = target
}
self.init(changesets)
}
}
// MARK: - MutableCollection
extension MutableCollection where Element: MutableCollection, Index == Int, Element.Index == Int {
@inlinable
internal subscript<O>(path: Internals.DiffableDataUIDispatcher<O>.ElementPath) -> Element.Element {
get { return self[path.section][path.element] }
set { self[path.section][path.element] = newValue }
}
}
#endif

View File

@@ -0,0 +1,229 @@
//
// Internals.DiffableDataUIDispatcher.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(QuartzCore)
import QuartzCore
#endif
// MARK: - Internals
extension Internals {
// MARK: Internal
// Implementation based on https://github.com/ra1028/DiffableDataSources
@usableFromInline
internal final class DiffableDataUIDispatcher<O: DynamicObject> {
// MARK: Internal
typealias ObjectType = O
init(dataStack: DataStack) {
self.dataStack = dataStack
}
func apply<View: AnyObject>(_ snapshot: DiffableDataSourceSnapshot, view: View?, animatingDifferences: Bool, performUpdates: @escaping (View, StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>, @escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void) -> Void) {
self.dispatcher.dispatch { [weak self] in
guard let self = self else {
return
}
self.currentSnapshot = snapshot
let newSections = snapshot.sections
guard let view = view else {
return self.sections = newSections
}
let performDiffingUpdates: () -> Void = {
let changeset = StagedChangeset(source: self.sections, target: newSections)
performUpdates(view, changeset) { sections in
self.sections = sections
}
}
#if canImport(QuartzCore)
if !animatingDifferences {
CATransaction.begin()
CATransaction.setDisableActions(true)
performDiffingUpdates()
CATransaction.commit()
return
}
#endif
performDiffingUpdates()
}
}
func snapshot() -> DiffableDataSourceSnapshot {
var snapshot: DiffableDataSourceSnapshot = .init()
snapshot.sections = self.currentSnapshot.sections
return snapshot
}
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
guard (0 ..< self.sections.endIndex) ~= indexPath.section else {
return nil
}
let items = self.sections[indexPath.section].elements
guard (0 ..< items.endIndex) ~= indexPath.item else {
return nil
}
return items[indexPath.item].differenceIdentifier
}
func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
let indexPathMap: [O.ObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
for (itemIndex, item) in section.element.elements.enumerated() {
result[item.differenceIdentifier] = IndexPath(
item: itemIndex,
section: section.offset
)
}
}
return indexPathMap[itemIdentifier]
}
func numberOfSections() -> Int {
return self.sections.count
}
func numberOfItems(inSection section: Int) -> Int {
return self.sections[section].elements.count
}
func sectionIdentifier(inSection section: Int) -> String {
return self.sections[section].differenceIdentifier
}
// MARK: Private
private let dispatcher: MainThreadSerialDispatcher = .init()
private let dataStack: DataStack
private var currentSnapshot: Internals.DiffableDataSourceSnapshot = .init()
private var sections: [Internals.DiffableDataSourceSnapshot.Section] = []
// MARK: - ElementPath
@usableFromInline
internal struct ElementPath: Hashable {
@usableFromInline
var element: Int
@usableFromInline
var section: Int
@inlinable
init(element: Int, section: Int) {
self.element = element
self.section = section
}
}
// MARK: - MainThreadSerialDispatcher
fileprivate final class MainThreadSerialDispatcher {
// MARK: FilePrivate
fileprivate init() {
self.executingCount.initialize(to: 0)
}
deinit {
self.executingCount.deinitialize(count: 1)
self.executingCount.deallocate()
}
fileprivate func dispatch(_ action: @escaping () -> Void) {
let count = OSAtomicIncrement32(self.executingCount)
if Thread.isMainThread && count == 1 {
action()
OSAtomicDecrement32(executingCount)
}
else {
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
action()
OSAtomicDecrement32(self.executingCount)
}
}
}
// MARK: Private
private let executingCount: UnsafeMutablePointer<Int32> = .allocate(capacity: 1)
}
}
}
#endif

View File

@@ -1,157 +0,0 @@
//
// Internals.FallbackDiffableDataUIDispatcher.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(QuartzCore)
import QuartzCore
#endif
// MARK: - Internals
extension Internals {
// MARK: Internal
// // Implementation based on https://github.com/ra1028/DiffableDataSources
// internal final class FallbackDiffableDataUIDispatcher {
//
// // MARK: Internal
//
// internal func apply() {
//
// }
//
// func snapshot() -> DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> {
// var snapshot = DiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
// snapshot.structure.sections = currentSnapshot.structure.sections
// return snapshot
// }
//
// func itemIdentifier(for indexPath: IndexPath) -> ItemIdentifierType? {
// guard 0..<sections.endIndex ~= indexPath.section else {
// return nil
// }
//
// let items = sections[indexPath.section].elements
//
// guard 0..<items.endIndex ~= indexPath.item else {
// return nil
// }
//
// return items[indexPath.item].differenceIdentifier
// }
//
// func unsafeItemIdentifier(for indexPath: IndexPath, file: StaticString = #file, line: UInt = #line) -> ItemIdentifierType {
// guard let itemIdentifier = itemIdentifier(for: indexPath) else {
// universalError("Item not found at the specified index path(\(indexPath)).")
// }
//
// return itemIdentifier
// }
//
// func indexPath(for itemIdentifier: ItemIdentifierType) -> IndexPath? {
// let indexPathMap: [ItemIdentifierType: IndexPath] = sections.enumerated()
// .reduce(into: [:]) { result, section in
// for (itemIndex, item) in section.element.elements.enumerated() {
// result[item.differenceIdentifier] = IndexPath(
// item: itemIndex,
// section: section.offset
// )
// }
// }
// return indexPathMap[itemIdentifier]
// }
//
// func numberOfSections() -> Int {
// return sections.count
// }
//
// func numberOfItems(inSection section: Int) -> Int {
// return sections[section].elements.count
// }
//
//
//
// // MARK: Private
//
// private let dispatcher: MainThreadSerialDispatcher = .init()
//
// private var currentSnapshot: Internals.DiffableDataSourceSnapshot = FallbackDiffableDataSourceSnapshot()
// private var sections: [FallbackDiffableDataSourceSnapshot.BackingStructure.Section] = []
//
//
// // MARK: - MainThreadSerialDispatcher
//
// fileprivate final class MainThreadSerialDispatcher {
//
// // MARK: FilePrivate
//
// fileprivate init() {
//
// self.executingCount.initialize(to: 0)
// }
//
// deinit {
//
// self.executingCount.deinitialize(count: 1)
// self.executingCount.deallocate()
// }
//
// fileprivate func dispatch(_ action: @escaping () -> Void) {
//
// let count = OSAtomicIncrement32(self.executingCount)
// if Thread.isMainThread && count == 1 {
//
// action()
// OSAtomicDecrement32(executingCount)
// }
// else {
//
// DispatchQueue.main.async { [weak self] in
//
// guard let self = self else {
//
// return
// }
// action()
// OSAtomicDecrement32(self.executingCount)
// }
// }
// }
//
//
// // MARK: Private
//
// private let executingCount: UnsafeMutablePointer<Int32> = .allocate(capacity: 1)
// }
// }
}
#endif

View File

@@ -41,7 +41,12 @@ import AppKit
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: DiffableDataSourceSnapshotProtocol)
// @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?
}
@@ -75,14 +80,14 @@ 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
// #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 {
@@ -94,19 +99,19 @@ 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<NSString, NSManagedObjectID>
)
}
#endif
// #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>) {
@@ -118,5 +123,14 @@ extension Internals {
)
)
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
return self.handler?.controller(
controller,
sectionIndexTitleForSectionName: sectionName
)
}
}
}

View File

@@ -56,18 +56,19 @@ extension Internals {
deinit {
self.observer.map(NotificationCenter.default.removeObserver(_:))
self.observers.removeAllObjects()
}
internal func addObserver<U: AnyObject>(_ observer: U, closure: @escaping (T) -> Void) {
self.observers.setObject(Closure(closure), forKey: observer)
self.observers.setObject(Closure<T, Void>(closure), forKey: observer)
}
// MARK: Private
private var observer: NSObjectProtocol!
private let observers: NSMapTable<AnyObject, Closure> = .weakToStrongObjects()
private let observers: NSMapTable<AnyObject, Closure<T, Void>> = .weakToStrongObjects()
private func notifyObservers(_ sharedValue: T) {
@@ -77,31 +78,8 @@ extension Internals {
}
for closure in enumerator {
(closure as! Closure).invoke(with: sharedValue)
(closure as! Closure<T, Void>).invoke(with: sharedValue)
}
}
// MARK: - Closure
fileprivate final class Closure {
// MARK: FilePrivate
fileprivate init(_ closure: @escaping (T) -> Void) {
self.closure = closure
}
fileprivate func invoke(with argument: T) {
self.closure(argument)
}
// MARK: Private
private let closure: (T) -> Void
}
}
}

View File

@@ -27,6 +27,7 @@ import Foundation
// MARK: - Internals
@usableFromInline
internal enum Internals {
// MARK: Associated Objects

View File

@@ -1453,7 +1453,7 @@ extension ListMonitor: FetchedResultsControllerHandler {
)
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
return self.sectionIndexTransformer(sectionName)
}

View File

@@ -42,6 +42,24 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
public typealias SectionID = String
public typealias ItemID = O.ObjectID
public init(byCloning snapshot: ListSnapshot<O>, for dataStack: DataStack) {
// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
//
// self.init(
// diffableSnapshot: snapshot.diffableSnapshot as! NSDiffableDataSourceSnapshot<String, NSManagedObjectID>,
// context: dataStack.mainContext
// )
// }
// else {
self.init(
diffableSnapshot: snapshot.diffableSnapshot as! Internals.DiffableDataSourceSnapshot,
context: dataStack.mainContext
)
// }
}
public subscript<S: Sequence>(indices indices: S) -> [LiveObject<O>] where S.Element == Index {
@@ -252,13 +270,29 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
// MARK: Internal
internal private(set) var diffableSnapshot: DiffableDataSourceSnapshotProtocol
internal init() {
self.diffableSnapshot = Internals.DiffableDataSourceSnapshot()
// 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.context = nil
}
internal init(diffableSnapshot: DiffableDataSourceSnapshotProtocol, context: NSManagedObjectContext) {
// @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
self.context = context
@@ -270,5 +304,4 @@ public struct ListSnapshot<O: DynamicObject>: SnapshotResult, RandomAccessCollec
private let id: UUID = .init()
private let context: NSManagedObjectContext?
private var diffableSnapshot: DiffableDataSourceSnapshotProtocol
}

View File

@@ -45,29 +45,6 @@ public final class LiveList<O: DynamicObject>: Hashable {
public typealias SectionID = SnapshotType.SectionID
public typealias ItemID = SnapshotType.ItemID
public fileprivate(set) var snapshot: SnapshotType = .init() {
willSet {
self.willChange()
}
}
public var numberOfItems: Int {
return self.snapshot.numberOfItems
}
public var numberOfSections: Int {
return self.snapshot.numberOfSections
}
public var sectionIdentifiers: [SectionID] {
return self.snapshot.sectionIdentifiers
}
public subscript(section sectionID: SectionID) -> [LiveObject<O>] {
let context = self.context
@@ -85,6 +62,24 @@ public final class LiveList<O: DynamicObject>: Hashable {
return self.context.liveObject(id: validID)
}
public subscript(indexPath indexPath: IndexPath) -> LiveObject<O>? {
let snapshot = self.snapshot
let sectionIdentifiers = snapshot.sectionIdentifiers
guard sectionIdentifiers.indices.contains(indexPath.section) else {
return nil
}
let sectionID = sectionIdentifiers[indexPath.section]
let itemIdentifiers = snapshot.itemIdentifiers(inSection: sectionID)
guard itemIdentifiers.indices.contains(indexPath.item) else {
return nil
}
let itemID = itemIdentifiers[indexPath.item]
return self.context.liveObject(id: itemID)
}
public subscript<S: Sequence>(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject<O>] where S.Element == Int {
let context = self.context
@@ -96,6 +91,34 @@ public final class LiveList<O: DynamicObject>: Hashable {
}
}
public fileprivate(set) var snapshot: SnapshotType = .init() {
willSet {
self.willChange()
}
didSet {
self.notifyObservers(self.snapshot)
self.didChange()
}
}
public var numberOfItems: Int {
return self.snapshot.numberOfItems
}
public var numberOfSections: Int {
return self.snapshot.numberOfSections
}
public var sectionIdentifiers: [SectionID] {
return self.snapshot.sectionIdentifiers
}
public var items: [LiveObject<O>] {
let context = self.context
@@ -141,6 +164,26 @@ public final class LiveList<O: DynamicObject>: Hashable {
return self.snapshot.indexOfSection(identifier)
}
public func addObserver<T: AnyObject>(_ observer: T, _ callback: @escaping (LiveList<O>, ListSnapshot<O>) -> Void) {
self.observers.setObject(
Internals.Closure(callback),
forKey: observer
)
callback(self, self.snapshot)
}
public func removeObserver<T: AnyObject>(_ observer: T) {
self.observers.removeObject(forKey: observer)
}
deinit {
self.observers.removeAllObjects()
}
// MARK: Equatable
@@ -222,12 +265,14 @@ public final class LiveList<O: DynamicObject>: Hashable {
private var fetchedResultsController: Internals.CoreStoreFetchedResultsController
private var fetchedResultsControllerDelegate: Internals.FetchedDiffableDataSourceSnapshotDelegate
private let sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String?
private var applyFetchClauses: (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
private let from: From<ObjectType>
private let sectionBy: SectionBy<ObjectType>?
private let observers: NSMapTable<AnyObject, Internals.Closure<(LiveList<O>, ListSnapshot<O>), Void>> = .weakToStrongObjects()
private lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
@@ -279,20 +324,31 @@ public final class LiveList<O: DynamicObject>: Hashable {
self.rawObjectWillChange = nil
}
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
// if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
//
// self.sectionIndexTransformer = sectionIndexTransformer
// }
// else {
//
// self.sectionIndexTransformer = { $0 }
// }
self.sectionIndexTransformer = sectionIndexTransformer
}
else {
self.sectionIndexTransformer = { $0 }
}
self.applyFetchClauses = applyFetchClauses
self.fetchedResultsControllerDelegate.handler = self
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
}
private func notifyObservers(_ snapshot: ListSnapshot<O>) {
guard let enumerator = self.observers.objectEnumerator() else {
return
}
for closure in enumerator {
(closure as! Internals.Closure<(LiveList<O>, ListSnapshot<O>), Void>).invoke(with: (self, snapshot))
}
}
}
@@ -301,14 +357,28 @@ public final class LiveList<O: DynamicObject>: Hashable {
extension LiveList: 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: DiffableDataSourceSnapshotProtocol) {
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
self.snapshot = .init(
diffableSnapshot: snapshot,
context: controller.managedObjectContext
)
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
return self.sectionIndexTransformer(sectionName)
}
}