mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-19 07:27:02 +01:00
(WIP) SwiftUI working demo for LiveList<D>
This commit is contained in:
@@ -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 */,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
683
CoreStoreTests/LiveListTests.swift
Normal file
683
CoreStoreTests/LiveListTests.swift
Normal 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
|
||||
|
||||
29
Sources/CoreStoreObject+DataSources.swift
Normal file
29
Sources/CoreStoreObject+DataSources.swift
Normal 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
|
||||
171
Sources/DataStack+DataSources.swift
Normal file
171
Sources/DataStack+DataSources.swift
Normal 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
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user