(WIP) SwiftUI working demo for LiveList<D>

This commit is contained in:
John Estropia
2019-10-04 19:12:32 +09:00
parent c5a996d5ed
commit 953c9723a8
12 changed files with 1422 additions and 347 deletions

View File

@@ -74,10 +74,10 @@
B501322B2346A9AE00FC238B /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132292344ECB500FC238B /* LiveList.swift */; };
B501322D2346A9B000FC238B /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132292344ECB500FC238B /* LiveList.swift */; };
B501322E2346A9B100FC238B /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132292344ECB500FC238B /* LiveList.swift */; };
B50132302346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.DiffDelegate.swift */; };
B50132312346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.DiffDelegate.swift */; };
B50132322346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.DiffDelegate.swift */; };
B50132332346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.DiffDelegate.swift */; };
B50132302346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */; };
B50132312346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */; };
B50132322346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */; };
B50132332346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */; };
B501FDDD1CA8D05000BE22EF /* CSSectionBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501FDDC1CA8D05000BE22EF /* CSSectionBy.swift */; };
B501FDDF1CA8D05000BE22EF /* CSSectionBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501FDDC1CA8D05000BE22EF /* CSSectionBy.swift */; };
B501FDE01CA8D05000BE22EF /* CSSectionBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501FDDC1CA8D05000BE22EF /* CSSectionBy.swift */; };
@@ -95,6 +95,10 @@
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 */; };
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 */; };
B50EE14523473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */; };
B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */; };
B51260801E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */; };
B51260811E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B512607E1E97A18000402229 /* CoreStoreObject+Convenience.swift */; };
@@ -578,6 +582,13 @@
B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
B5D7A5B91CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
B5D7A5BA1CA3BF8F005C752B /* CSInto.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D7A5B51CA3BF8F005C752B /* CSInto.swift */; };
B5D8CA762346E7590055D7D1 /* DataStack+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */; };
B5D8CA772346EAEE0055D7D1 /* DataStack+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */; };
B5D8CA782346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */; };
B5D8CA792346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */; };
B5D8CA7C2346EC5F0055D7D1 /* LiveListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA7A2346EC550055D7D1 /* LiveListTests.swift */; };
B5D8CA7D2346EC610055D7D1 /* LiveListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA7A2346EC550055D7D1 /* LiveListTests.swift */; };
B5D8CA7E2346EC610055D7D1 /* LiveListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D8CA7A2346EC550055D7D1 /* LiveListTests.swift */; };
B5DAFB482203D9F8003FCCD0 /* Where.Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAFB472203D9F8003FCCD0 /* Where.Expression.swift */; };
B5DAFB4A2203E01D003FCCD0 /* KeyPathGenericBindings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAFB492203E01D003FCCD0 /* KeyPathGenericBindings.swift */; };
B5DBE2CD1C9914A900B5CEFA /* CSCoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DBE2CC1C9914A900B5CEFA /* CSCoreStore.swift */; };
@@ -784,13 +795,14 @@
82BA18E01C4BBE2C00A0916E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.1.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; };
B500810F2290CDF800F4CEA5 /* bitrise.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = bitrise.yml; sourceTree = SOURCE_ROOT; };
B50132292344ECB500FC238B /* LiveList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveList.swift; sourceTree = "<group>"; };
B501322F2346B76E00FC238B /* Internals.DiffDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffDelegate.swift; sourceTree = "<group>"; };
B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.FetchedDiffableDataSourceSnapshotDelegate.swift; sourceTree = "<group>"; };
B501FDDC1CA8D05000BE22EF /* CSSectionBy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSectionBy.swift; sourceTree = "<group>"; };
B501FDE11CA8D1F500BE22EF /* CSListMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSListMonitor.swift; sourceTree = "<group>"; };
B501FDE61CA8D20500BE22EF /* CSListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSListObserver.swift; sourceTree = "<group>"; };
B50392F81C478FF3009900CA /* NSManagedObject+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Transaction.swift"; sourceTree = "<group>"; };
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; };
B509C7F31E54511B0061C547 /* ImportableAttributeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportableAttributeType.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>"; };
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.EntityIdentifier.swift; sourceTree = "<group>"; };
@@ -916,6 +928,8 @@
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
B5D7A5B51CA3BF8F005C752B /* CSInto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSInto.swift; sourceTree = "<group>"; };
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+DataSources.swift"; sourceTree = "<group>"; };
B5D8CA7A2346EC550055D7D1 /* LiveListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveListTests.swift; sourceTree = "<group>"; };
B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = CoreStore.podspec; sourceTree = SOURCE_ROOT; };
B5DAFB472203D9F8003FCCD0 /* Where.Expression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Where.Expression.swift; sourceTree = "<group>"; };
B5DAFB492203E01D003FCCD0 /* KeyPathGenericBindings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPathGenericBindings.swift; sourceTree = "<group>"; };
@@ -1128,6 +1142,7 @@
B5220E0B1D0D0D19009BC71E /* ImportTests.swift */,
B525576B1CFAF18F00E51965 /* IntoTests.swift */,
B5220E0F1D0DA6AB009BC71E /* ListObserverTests.swift */,
B5D8CA7A2346EC550055D7D1 /* LiveListTests.swift */,
B5DC47C51C93D22900FA3BF3 /* MigrationChainTests.swift */,
B5220E071D0C5F8D009BC71E /* ObjectObserverTests.swift */,
B52557771D02826E00E51965 /* OrderByTests.swift */,
@@ -1442,7 +1457,10 @@
B5E5FA4C22D15D3C00330931 /* DataSources */ = {
isa = PBXGroup;
children = (
B50132292344ECB500FC238B /* LiveList.swift */,
B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */,
B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */,
B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */,
);
name = DataSources;
sourceTree = "<group>";
@@ -1546,7 +1564,6 @@
B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */,
B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */,
B5E84F1D1AFF84860064E85B /* ListMonitor.swift */,
B50132292344ECB500FC238B /* LiveList.swift */,
B5E84F1E1AFF84860064E85B /* ListObserver.swift */,
);
name = Observing;
@@ -1575,7 +1592,7 @@
B5474D142227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift */,
B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */,
B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */,
B501322F2346B76E00FC238B /* Internals.DiffDelegate.swift */,
B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */,
B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */,
B5E84F2B1AFF849C0064E85B /* Internals.NotificationObserver.swift */,
B5DE522A230BD7CC00A22534 /* Internals.swift */,
@@ -1954,9 +1971,10 @@
B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
B5831B751F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */,
B5D8CA762346E7590055D7D1 /* DataStack+DataSources.swift in Sources */,
B5E5FA4E22D162F400330931 /* ObjectSnapshot.swift in Sources */,
B5E1B5A81CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
B50132302346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */,
B50132302346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */,
B5D339F11E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */,
B5E1B59D1CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
@@ -2002,6 +2020,7 @@
B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */,
B52F74451E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */,
B5FAD6AC1B51285300714891 /* Internals.MigrationManager.swift in Sources */,
B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */,
B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */,
B596BBB61DD5BC67001DCDD9 /* FetchableSource.swift in Sources */,
@@ -2087,6 +2106,7 @@
B5220E101D0DA6AB009BC71E /* ListObserverTests.swift in Sources */,
B5519A401CA1B17B002BEF78 /* ErrorTests.swift in Sources */,
B525577C1D0291FE00E51965 /* GroupByTests.swift in Sources */,
B5D8CA7C2346EC5F0055D7D1 /* LiveListTests.swift in Sources */,
B52557741D02791400E51965 /* WhereTests.swift in Sources */,
B5DC47C61C93D22900FA3BF3 /* MigrationChainTests.swift in Sources */,
B525576C1CFAF18F00E51965 /* IntoTests.swift in Sources */,
@@ -2159,6 +2179,7 @@
B549F6741E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F51E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
82BA18B31C4BBD3900A0916E /* ImportableUniqueObject.swift in Sources */,
B50EE14323473C96009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B5E1B5951CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
18166887232B9ED60097C275 /* String+KeyPaths.swift in Sources */,
B5ECDC2B1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
@@ -2184,7 +2205,7 @@
82BA18C11C4BBD5300A0916E /* CoreStore+Observing.swift in Sources */,
82BA18BC1C4BBD4A00A0916E /* OrderBy.swift in Sources */,
82BA18B01C4BBD3100A0916E /* NSManagedObject+Transaction.swift in Sources */,
B50132312346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */,
B50132312346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */,
82BA18D41C4BBD7100A0916E /* NSManagedObjectContext+Querying.swift in Sources */,
82BA18D51C4BBD7100A0916E /* NSManagedObjectContext+Setup.swift in Sources */,
B501FDE91CA8D20500BE22EF /* CSListObserver.swift in Sources */,
@@ -2195,6 +2216,7 @@
B5ECDC131CA816E500C7F112 /* CSTweak.swift in Sources */,
B56923C51EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
82BA18C91C4BBD5900A0916E /* MigrationType.swift in Sources */,
B5D8CA772346EAEE0055D7D1 /* DataStack+DataSources.swift in Sources */,
82BA18D01C4BBD7100A0916E /* Internals.MigrationManager.swift in Sources */,
B5DE5231230BDA1300A22534 /* Shared.swift in Sources */,
B52F74461E9B8724005F3DAC /* XcodeDataModelSchema.swift in Sources */,
@@ -2287,6 +2309,7 @@
B5220E111D0DA6AB009BC71E /* ListObserverTests.swift in Sources */,
B5519A411CA1B17B002BEF78 /* ErrorTests.swift in Sources */,
B525577D1D0291FE00E51965 /* GroupByTests.swift in Sources */,
B5D8CA7D2346EC610055D7D1 /* LiveListTests.swift in Sources */,
B52557751D02791400E51965 /* WhereTests.swift in Sources */,
B5DC47C71C93D22900FA3BF3 /* MigrationChainTests.swift in Sources */,
B5DBE2E01C9939E100B5CEFA /* BridgingTests.m in Sources */,
@@ -2359,6 +2382,7 @@
B549F6761E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F71E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
B5220E1F1D130810009BC71E /* CSListObserver.swift in Sources */,
B50EE14523473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */,
18166889232B9ED80097C275 /* String+KeyPaths.swift in Sources */,
B52DD1A61BE1F92F00949AFE /* BaseDataTransaction+Importing.swift in Sources */,
@@ -2384,7 +2408,7 @@
B52DD1C81BE1F94600949AFE /* NSManagedObjectContext+Setup.swift in Sources */,
B52DD1C31BE1F94600949AFE /* Internals.NotificationObserver.swift in Sources */,
B52DD1A81BE1F93200949AFE /* DataStack+Querying.swift in Sources */,
B50132332346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */,
B50132332346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */,
B5220E221D130818009BC71E /* CSSectionBy.swift in Sources */,
B52DD1BC1BE1F94000949AFE /* MigrationResult.swift in Sources */,
B52DD19D1BE1F92C00949AFE /* BaseDataTransaction.swift in Sources */,
@@ -2395,6 +2419,7 @@
B5A1DACB1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
B52DD1B81BE1F94000949AFE /* DataStack+Migration.swift in Sources */,
B5ECDC091CA8138100C7F112 /* CSOrderBy.swift in Sources */,
B5D8CA792346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */,
B56923C71EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
B5DE5233230BDA1300A22534 /* Shared.swift in Sources */,
B52DD1A51BE1F92F00949AFE /* ImportableUniqueObject.swift in Sources */,
@@ -2487,6 +2512,7 @@
B5220E271D1308D1009BC71E /* ObjectObserverTests.swift in Sources */,
B525577E1D0291FE00E51965 /* GroupByTests.swift in Sources */,
B52557761D02791400E51965 /* WhereTests.swift in Sources */,
B5D8CA7E2346EC610055D7D1 /* LiveListTests.swift in Sources */,
B5DC47C81C93D22900FA3BF3 /* MigrationChainTests.swift in Sources */,
B5DBE2E11C9939E100B5CEFA /* BridgingTests.m in Sources */,
B5220E0E1D0D0D19009BC71E /* ImportTests.swift in Sources */,
@@ -2559,6 +2585,7 @@
B549F6751E56A92800FBAB2D /* CoreDataNativeType.swift in Sources */,
B509C7F61E54511B0061C547 /* ImportableAttributeType.swift in Sources */,
B5E1B5961CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */,
B50EE14423473C97009B8C47 /* CoreStoreObject+DataSources.swift in Sources */,
B5ECDC2C1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */,
18166888232B9ED70097C275 /* String+KeyPaths.swift in Sources */,
B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */,
@@ -2584,7 +2611,7 @@
B56321971BD65216006C9394 /* Select.swift in Sources */,
B56321AB1BD6521C006C9394 /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
B563219C1BD65216006C9394 /* SectionBy.swift in Sources */,
B50132322346B76E00FC238B /* Internals.DiffDelegate.swift in Sources */,
B50132322346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */,
B56321B21BD6521C006C9394 /* NSManagedObjectContext+Querying.swift in Sources */,
B5FE4DA41C8481E100FA6A91 /* StorageInterface.swift in Sources */,
B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */,
@@ -2595,6 +2622,7 @@
B5A1DACA1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
B56321AE1BD6521C006C9394 /* Internals.NotificationObserver.swift in Sources */,
B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */,
B5D8CA782346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */,
B56923C61EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
B56321A71BD65216006C9394 /* MigrationResult.swift in Sources */,
B5DE5232230BDA1300A22534 /* Shared.swift in Sources */,

View File

@@ -52,7 +52,7 @@ struct ColorsDemo {
static let stack: DataStack = {
return DataStack(
let dataStack = DataStack(
CoreStoreSchema(
modelVersion: "ColorsDemo",
entities: [
@@ -63,16 +63,18 @@ struct ColorsDemo {
]
)
)
}()
static let palettes: ListMonitor<Palette> = {
try! ColorsDemo.stack.addStorageAndWait(
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return dataStack
}()
static let palettes: ListMonitor<Palette> = {
return ColorsDemo.stack.monitorSectionedList(
From<Palette>()
.sectionBy(\.colorName)

View File

@@ -10,12 +10,21 @@
import SwiftUI
import UIKit
import CoreStore
@available(iOS 13.0.0, *)
class SwiftUIHostingController: UIHostingController<SwiftUIView> {
@objc required dynamic init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder, rootView: SwiftUIView())
super.init(
coder: aDecoder,
rootView: SwiftUIView(
palettes: DataStackEnvironment.defaultValue.liveList(
From<Palette>()
.orderBy(.ascending(\.hue))
)
)
)
}
}

View File

@@ -11,11 +11,23 @@
import SwiftUI
import CoreStore
@available(iOS 13.0, *)
struct DataStackEnvironment: EnvironmentKey {
static let defaultValue = ColorsDemo.stack
}
@available(iOS 13.0.0, *)
extension EnvironmentValues {
var dataStack: DataStack {
return ColorsDemo.stack
get {
return self[DataStackEnvironment.self]
}
set {
self[DataStackEnvironment.self] = newValue
}
}
}
@@ -23,13 +35,13 @@ extension EnvironmentValues {
struct SwiftUIView: View {
@Environment(\.dataStack) var dataStack: DataStack
var palettes = ColorsDemo.palettes
@ObservedObject var palettes: LiveList<Palette>
var body: some View {
NavigationView {
List {
ForEach(palettes.objectsInAllSections(), id: \.self) { palette in
ForEach(palettes.snapshot, id: \.self) { palette in
NavigationLink(
destination: DetailView(palette: palette)
) {
@@ -40,7 +52,14 @@ struct SwiftUIView: View {
}
}
}.onDelete { indices in
// self.events.delete(at: indices, from: self.viewContext)
let palettes = self.palettes.snapshot[indices]
self.dataStack.perform(
asynchronous: { transaction in
palettes.forEach(transaction.delete(_:))
},
completion: { _ in }
)
}
}
.navigationBarTitle(Text("Master"))
@@ -48,7 +67,7 @@ struct SwiftUIView: View {
leading: EditButton(),
trailing: Button(
action: {
self.dataStack.perform(
asynchronous: { transaction in
@@ -83,7 +102,12 @@ struct DetailView: View {
@available(iOS 13.0.0, *)
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
SwiftUIView(
palettes: DataStackEnvironment.defaultValue.liveList(
From<Palette>()
.orderBy(.ascending(\.hue))
)
)
}
}

View File

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

View File

@@ -0,0 +1,29 @@
//
// CoreStoreObject+DataSources.swift
// CoreStore iOS
//
// Created by John Estropia on 2019/10/04.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
#if canImport(UIKit) || canImport(AppKit)
#if canImport(Combine)
import Combine
// MARK: - LiveList: ObservableObject
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension CoreStoreObject: ObservableObject {
// MARK: ObservableObject
public var objectWillChange: ObservableObjectPublisher {
return self.cs_toRaw().objectWillChange
}
}
#endif
#endif

View File

@@ -0,0 +1,171 @@
//
// DataStack+DataSources.swift
// CoreStore iOS
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
import CoreData
// MARK: - DataStack
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension DataStack {
public func liveList<D>(_ from: From<D>, _ fetchClauses: FetchClause...) -> LiveList<D> {
return self.liveList(from, fetchClauses)
}
public func liveList<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) -> LiveList<D> {
return LiveList(
dataStack: self,
from: from,
sectionBy: nil,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(LiveList<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
}
)
}
public func liveList<B: FetchChainableBuilderType>(_ clauseChain: B) -> LiveList<B.ObjectType> {
return self.liveList(
clauseChain.from,
clauseChain.fetchClauses
)
}
public func liveList<D>(createAsynchronously: @escaping (LiveList<D>) -> Void, _ from: From<D>, _ fetchClauses: FetchClause...) {
self.liveList(
createAsynchronously: createAsynchronously,
from, fetchClauses
)
}
public func liveList<D>(createAsynchronously: @escaping (LiveList<D>) -> Void, _ from: From<D>, _ fetchClauses: [FetchClause]) {
_ = LiveList(
dataStack: self,
from: from,
sectionBy: nil,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(LiveList<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
},
createAsynchronously: createAsynchronously
)
}
public func liveList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) -> LiveList<D> {
return self.liveList(
from,
sectionBy,
fetchClauses
)
}
public func liveList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: [FetchClause]) -> LiveList<D> {
return LiveList(
dataStack: self,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(LiveList<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
}
)
}
public func liveList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> LiveList<B.ObjectType> {
return self.liveList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
public func liveList<D>(createAsynchronously: @escaping (LiveList<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) {
self.liveList(
createAsynchronously: createAsynchronously,
from,
sectionBy,
fetchClauses
)
}
public func liveList<D>(createAsynchronously: @escaping (LiveList<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: [FetchClause]) {
_ = LiveList(
dataStack: self,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(LiveList<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
},
createAsynchronously: createAsynchronously
)
}
public func liveList<B: SectionMonitorBuilderType>(createAsynchronously: @escaping (LiveList<B.ObjectType>) -> Void, _ clauseChain: B) {
self.liveList(
createAsynchronously: createAsynchronously,
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
}
#endif

View File

@@ -102,7 +102,10 @@ extension DataStack {
*/
public func monitorList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListMonitor<B.ObjectType> {
return self.monitorList(clauseChain.from, clauseChain.fetchClauses)
return self.monitorList(
clauseChain.from,
clauseChain.fetchClauses
)
}
/**
@@ -114,7 +117,11 @@ extension DataStack {
*/
public func monitorList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ fetchClauses: FetchClause...) {
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
self.monitorList(
createAsynchronously: createAsynchronously,
from,
fetchClauses
)
}
/**
@@ -182,7 +189,11 @@ extension DataStack {
*/
public func monitorSectionedList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) -> ListMonitor<D> {
return self.monitorSectionedList(from, sectionBy, fetchClauses)
return self.monitorSectionedList(
from,
sectionBy,
fetchClauses
)
}
/**
@@ -248,7 +259,12 @@ extension DataStack {
*/
public func monitorSectionedList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) {
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
self.monitorSectionedList(
createAsynchronously: createAsynchronously,
from,
sectionBy,
fetchClauses
)
}
/**

View File

@@ -1,276 +0,0 @@
//
// Internals.FetchedResultsControllerDelegate.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - FetchedResultsControllerHandler
@available(macOS 10.12, *)
internal protocol FetchedResultsControllerHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String?
}
// MARK: - Internal
extension Internals {
// MARK: - FetchedResultsControllerDelegate
@available(macOS 10.12, *)
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@nonobjc
internal var enabled = true
@nonobjc
internal let taskGroup = DispatchGroup()
@nonobjc
internal weak var handler: FetchedResultsControllerHandler?
@nonobjc
internal weak var fetchedResultsController: Internals.CoreStoreFetchedResultsController? {
didSet {
oldValue?.delegate = nil
self.fetchedResultsController?.delegate = self
}
}
deinit {
self.fetchedResultsController?.delegate = nil
}
// MARK: NSFetchedResultsControllerDelegate
@available(iOS 13.0, *)
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshot) {
}
@objc
dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.taskGroup.enter()
guard self.enabled else {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
else {
self.deletedSections = []
self.insertedSections = []
}
self.handler?.controllerWillChangeContent(controller)
}
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
defer {
self.taskGroup.leave()
}
guard self.enabled else {
return
}
self.handler?.controllerDidChangeContent(controller)
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
guard self.enabled else {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: type,
newIndexPath: newIndexPath
)
return
}
guard var actualType = NSFetchedResultsChangeType(rawValue: type.rawValue) else {
// This fix is for a bug where iOS passes 0 for NSFetchedResultsChangeType, but this is not a valid enum case.
// Swift will then always execute the first case of the switch causing strange behaviour.
// https://forums.developer.apple.com/thread/12184#31850
return
}
// This whole dance is a workaround for a nasty bug introduced in XCode 7 targeted at iOS 8 devices
// http://stackoverflow.com/questions/31383760/ios-9-attempt-to-delete-and-reload-the-same-index-path/31384014#31384014
// https://forums.developer.apple.com/message/9998#9998
// https://forums.developer.apple.com/message/31849#31849
if case .update = actualType,
indexPath != nil,
newIndexPath != nil {
actualType = .move
}
switch actualType {
case .update:
guard let section = indexPath?[0] else {
return
}
if self.deletedSections.contains(section)
|| self.insertedSections.contains(section) {
return
}
case .move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {
return
}
guard indexPath == newIndexPath else {
break
}
if self.insertedSections.contains(indexPath[0]) {
// Observers that handle the .Move change are advised to delete then reinsert the object instead of just moving. This is especially true when indexPath and newIndexPath are equal. For example, calling tableView.moveRowAtIndexPath(_:toIndexPath) when both indexPaths are the same will crash the tableView.
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: .move,
newIndexPath: newIndexPath
)
return
}
if self.deletedSections.contains(indexPath[0]) {
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: nil,
forChangeType: .insert,
newIndexPath: indexPath
)
return
}
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: .update,
newIndexPath: nil
)
return
default:
break
}
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: actualType,
newIndexPath: newIndexPath
)
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
guard self.enabled else {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
else {
switch type {
case .delete: self.deletedSections.insert(sectionIndex)
case .insert: self.insertedSections.insert(sectionIndex)
default: break
}
}
self.handler?.controller(
controller,
didChangeSection: sectionInfo,
atIndex: sectionIndex,
forChangeType: type
)
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
return self.handler?.controller(
controller,
sectionIndexTitleForSectionName: sectionName
)
}
// MARK: Private
@nonobjc
private var deletedSections = Set<Int>()
@nonobjc
private var insertedSections = Set<Int>()
}
}

View File

@@ -0,0 +1,94 @@
//
// Internals.FetchedDiffableDataSourceSnapshotDelegate.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
import CoreData
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// MARK: - FetchedDiffableDataSourceSnapshot
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: NSDiffableDataSourceSnapshotReference)
}
// MARK: - Internal
extension Internals {
// MARK: - FetchedDiffableDataSourceSnapshotDelegate
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
internal final class FetchedDiffableDataSourceSnapshotDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@nonobjc
internal weak var handler: FetchedDiffableDataSourceSnapshotHandler?
@nonobjc
internal weak var fetchedResultsController: Internals.CoreStoreFetchedResultsController? {
didSet {
oldValue?.delegate = nil
self.fetchedResultsController?.delegate = self
}
}
deinit {
self.fetchedResultsController?.delegate = nil
}
// MARK: NSFetchedResultsControllerDelegate
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
self.handler?.controller(
controller,
didChangContentWith: snapshot
)
}
}
}
#endif

View File

@@ -948,7 +948,7 @@ public final class ListMonitor<D: DynamicObject>: Hashable {
return
}
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController(
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
context: self.fetchedResultsController.managedObjectContext,
from: self.from,
sectionBy: self.sectionBy,
@@ -1083,7 +1083,7 @@ public final class ListMonitor<D: DynamicObject>: Hashable {
self.isSectioned = (sectionBy != nil)
self.from = from
self.sectionBy = sectionBy
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = ListMonitor.recreateFetchedResultsController(
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
context: context,
from: from,
sectionBy: sectionBy,

View File

@@ -23,16 +23,28 @@
// SOFTWARE.
//
#if canImport(Combine)
#if canImport(UIKit) || canImport(AppKit)
import Combine
import CoreData
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - LiveList
@available(iOS 13.0, *)
public class LiveList<D: DynamicObject>: Hashable, ObservableObject {
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
public final class LiveList<D: DynamicObject>: Hashable {
// MARK: Public (Accessors)
@@ -41,38 +53,272 @@ public class LiveList<D: DynamicObject>: Hashable, ObservableObject {
*/
public typealias ObjectType = D
public var snapshot: Snapshot = []
// MARK: ObservableObject
public var objectWillChange: ObservableObjectPublisher {
return self.cs_toRaw().objectWillChange
public fileprivate(set) var snapshot: Snapshot = .empty {
didSet {
#if canImport(Combine)
let newValue = self.snapshot
guard newValue != oldValue else {
return
}
#if canImport(SwiftUI)
withAnimation {
self.objectWillChange.send()
}
#else
self.objectWillChange.send()
#endif
#endif
}
}
// MARK: Equatable
public static func == (_ lhs: LiveList<D>, _ rhs: LiveList<D>) -> Bool {
return lhs === rhs
}
// MARK: Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
// MARK: Internal
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
self.init(
context: dataStack.mainContext,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses,
createAsynchronously: nil
)
}
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (LiveList<ObjectType>) -> Void) {
self.init(
context: dataStack.mainContext,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses,
createAsynchronously: createAsynchronously
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
self.init(
context: unsafeTransaction.context,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses,
createAsynchronously: nil
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (LiveList<ObjectType>) -> Void) {
self.init(
context: unsafeTransaction.context,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses,
createAsynchronously: createAsynchronously
)
}
// MARK: FilePrivate
fileprivate let rawObjectWillChange: Any?
// MARK: Private
private let observer: Internals.FetchedResultsControllerDelegate
private var fetchedResultsController: Internals.CoreStoreFetchedResultsController
private var fetchedResultsControllerDelegate: Internals.FetchedDiffableDataSourceSnapshotDelegate
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 static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate) {
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .managedObjectResultType
fetchRequest.includesPendingChanges = false
fetchRequest.shouldRefreshRefetchedObjects = true
let fetchedResultsController = Internals.CoreStoreFetchedResultsController(
context: context,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses
)
let fetchedResultsControllerDelegate = Internals.FetchedDiffableDataSourceSnapshotDelegate()
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
return (fetchedResultsController, fetchedResultsControllerDelegate)
}
private init(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((LiveList<ObjectType>) -> Void)?) {
self.from = from
self.sectionBy = sectionBy
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
context: context,
from: from,
sectionBy: sectionBy,
applyFetchClauses: applyFetchClauses
)
#if canImport(Combine)
self.rawObjectWillChange = ObservableObjectPublisher()
#else
self.rawObjectWillChange = nil
#endif
// if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
//
// self.sectionIndexTransformer = sectionIndexTransformer
// }
// else {
//
// self.sectionIndexTransformer = { $0 }
// }
self.applyFetchClauses = applyFetchClauses
self.fetchedResultsControllerDelegate.handler = self
guard let coordinator = context.parentStack?.coordinator else {
return
}
self.observerForWillChangePersistentStore = Internals.NotificationObserver(
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresWillChange,
object: coordinator,
queue: OperationQueue.main,
closure: { [weak self] (note) -> Void in
guard let `self` = self else {
return
}
// self.isPersistentStoreChanging = true
//
// guard let removedStores = (note.userInfo?[NSRemovedPersistentStoresKey] as? [NSPersistentStore]).flatMap(Set.init),
// !Set(self.fetchedResultsController.typedFetchRequest.safeAffectedStores() ?? []).intersection(removedStores).isEmpty else {
//
// return
// }
// self.refetch(self.applyFetchClauses)
}
)
self.observerForDidChangePersistentStore = Internals.NotificationObserver(
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresDidChange,
object: coordinator,
queue: OperationQueue.main,
closure: { [weak self] (note) -> Void in
guard let `self` = self else {
return
}
// if !self.isPendingRefetch {
//
// let previousStores = Set(self.fetchedResultsController.typedFetchRequest.safeAffectedStores() ?? [])
// let currentStores = previousStores
// .subtracting(note.userInfo?[NSRemovedPersistentStoresKey] as? [NSPersistentStore] ?? [])
// .union(note.userInfo?[NSAddedPersistentStoresKey] as? [NSPersistentStore] ?? [])
//
// if previousStores != currentStores {
//
// self.refetch(self.applyFetchClauses)
// }
// }
//
// self.isPersistentStoreChanging = false
}
)
if let createAsynchronously = createAsynchronously {
// transactionQueue.async {
//
// try! self.fetchedResultsController.performFetchFromSpecifiedStores()
// self.taskGroup.notify(queue: .main) {
//
// createAsynchronously(self)
// }
// }
}
else {
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
}
}
// MARK: - Snapshot
public struct Snapshot: RandomAccessCollection {
public struct Snapshot: RandomAccessCollection, Hashable {
public subscript(indices: IndexSet) -> [ObjectType] {
let context = self.context!
let objectIDs = self.snapshotStruct.itemIdentifiers
return indices.map { position in
let objectID = objectIDs[position]
return context.fetchExisting(objectID)!
}
}
// MARK: RandomAccessCollection
public var startIndex: Index {
return 0
}
public var endIndex: Index {
return self.snapshotStruct.numberOfItems
}
public subscript(position: Index) -> ObjectType {
let context = self.context!
let objectID = self.snapshotStruct.itemIdentifiers[position]
return context.fetchExisting(objectID)!
}
@@ -95,36 +341,85 @@ public class LiveList<D: DynamicObject>: Hashable, ObservableObject {
// /// supplying `IndexingIterator` as its associated `Iterator`
// /// type.
// public typealias Iterator = IndexingIterator<FetchedResults<Result>>
private let diffableSource: NSDiffable
}
}
extension ListMonitor: ObservableObject {
public var objectWillChange: ObservableObjectPublisher {
// MARK: Equatable
public static func == (_ lhs: Snapshot, _ rhs: Snapshot) -> Bool {
return lhs.snapshotReference == rhs.snapshotReference
}
// MARK: Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(self.snapshotReference)
}
// MARK: Internal
internal static var empty: Snapshot {
return .init()
}
internal init(snapshotReference: NSDiffableDataSourceSnapshotReference, context: NSManagedObjectContext) {
self.snapshotReference = snapshotReference
self.snapshotStruct = snapshotReference as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
self.context = context
}
// MARK: Private
return withUnsafePointer(to: &Static.objectWillChange) {
self.userInfo[
$0,
lazyInit: ObservableObjectPublisher.init
] as! ObservableObjectPublisher
private let snapshotReference: NSDiffableDataSourceSnapshotReference
private let snapshotStruct: NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
private let context: NSManagedObjectContext?
private init() {
self.snapshotReference = .init()
self.snapshotStruct = self.snapshotReference as NSDiffableDataSourceSnapshot<NSString, NSManagedObjectID>
self.context = nil
}
}
}
@available(iOS 13.0, *)
extension CoreStoreObject: ObservableObject {
public var objectWillChange: ObservableObjectPublisher {
return self.cs_toRaw().objectWillChange
// MARK: - LiveList: FetchedDiffableDataSourceSnapshotHandler
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension LiveList: FetchedDiffableDataSourceSnapshotHandler {
// MARK: FetchedDiffableDataSourceSnapshotHandler
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
self.snapshot = .init(snapshotReference: snapshot, context: controller.managedObjectContext)
}
}
fileprivate enum Static {
static var objectWillChange: Void?
#if canImport(Combine)
import Combine
// MARK: - LiveList: ObservableObject
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *)
extension LiveList: ObservableObject {
// MARK: ObservableObject
public var objectWillChange: ObservableObjectPublisher {
return self.rawObjectWillChange! as! ObservableObjectPublisher
}
}
#endif
#endif