diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index c96128e..7a4a8d6 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -188,6 +188,14 @@ B52557881D02DE8100E51965 /* FetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52557871D02DE8100E51965 /* FetchTests.swift */; }; B52557891D02DE8100E51965 /* FetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52557871D02DE8100E51965 /* FetchTests.swift */; }; B525578A1D02DE8100E51965 /* FetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52557871D02DE8100E51965 /* FetchTests.swift */; }; + B5277672234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277671234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift */; }; + B5277673234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277671234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift */; }; + B5277674234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277671234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift */; }; + B5277675234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277671234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift */; }; + B5277677234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */; }; + B5277678234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */; }; + B5277679234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */; }; + B527767A234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */; }; B52DD17E1BE1F8CD00949AFE /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52DD1741BE1F8CC00949AFE /* CoreStore.framework */; }; B52DD1931BE1F8FD00949AFE /* CoreStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F03A53519C5C6DA005002A5 /* CoreStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; @@ -552,6 +560,18 @@ B5BF7FBD234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; }; B5BF7FBE234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; }; B5BF7FBF234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */; }; + B5BF7FC1234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; }; + B5BF7FC2234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; }; + B5BF7FC3234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; }; + B5BF7FC4234D7B2E0070E741 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */; }; + B5BF7FC6234D7E460070E741 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */; }; + B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */; }; + B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */; }; + B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */; }; + B5BF7FCB234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */; }; + B5BF7FCC234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */; }; + B5BF7FCD234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */; }; + B5BF7FCE234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */; }; B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */; }; B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */; }; B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */; }; @@ -670,10 +690,6 @@ B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */; }; - B5E5FA4E22D162F400330931 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */; }; - B5E5FA4F22D162F400330931 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */; }; - B5E5FA5022D162F400330931 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */; }; - B5E5FA5122D162F400330931 /* ObjectSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */; }; B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; }; B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; }; @@ -872,6 +888,8 @@ B525577F1D029D2500E51965 /* TweakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TweakTests.swift; sourceTree = ""; }; B52557831D02A07400E51965 /* SectionByTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionByTests.swift; sourceTree = ""; }; B52557871D02DE8100E51965 /* FetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchTests.swift; sourceTree = ""; }; + B5277671234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Logging.swift"; sourceTree = ""; }; + B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.SharedNotificationObserver.swift; sourceTree = ""; }; B52DD1741BE1F8CC00949AFE /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B52DD17D1BE1F8CC00949AFE /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaHistory.swift; sourceTree = ""; }; @@ -962,6 +980,9 @@ B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.swift; sourceTree = ""; }; B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.TableView.swift; sourceTree = ""; }; B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.FallbackDiffableDataUIDispatcher.swift; sourceTree = ""; }; + B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveObject.swift; sourceTree = ""; }; + B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = ""; }; + B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.LazyNonmutating.swift; sourceTree = ""; }; B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeDataTransaction+Observing.swift"; sourceTree = ""; }; B5C976E61C6E3A5900B1AF90 /* Internals.CoreStoreFetchedResultsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Internals.CoreStoreFetchedResultsController.swift; sourceTree = ""; }; B5CA2B071F7E5ACA004B1936 /* WhereClauseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhereClauseType.swift; sourceTree = ""; }; @@ -1002,7 +1023,6 @@ B5E222291CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSUnsafeDataTransaction.swift; sourceTree = ""; }; B5E294DC2349F8E7003E5956 /* SnapshotResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotResult.swift; sourceTree = ""; }; B5E41EBF1EA9BB37006240F0 /* DynamicSchema+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DynamicSchema+Convenience.swift"; sourceTree = ""; }; - B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = ""; }; B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = ""; }; B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; B5E84ED91AFF82360064E85B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; }; @@ -1527,9 +1547,10 @@ B5F849702348A6690029D57B /* EnvironmentKeys.swift */, B5F84966234887EB0029D57B /* LiveQuery.swift */, B50132292344ECB500FC238B /* LiveList.swift */, + B5BF7FC0234D7B2E0070E741 /* LiveObject.swift */, B5F8496B234898240029D57B /* ListSnapshot.swift */, + B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */, B50EE14123473C92009B8C47 /* CoreStoreObject+DataSources.swift */, - B5E5FA4D22D162F400330931 /* ObjectSnapshot.swift */, B5D8CA752346E7590055D7D1 /* DataStack+DataSources.swift */, B5F335572348D75D00FD649F /* LiveResult.swift */, B5E294DC2349F8E7003E5956 /* SnapshotResult.swift */, @@ -1664,13 +1685,15 @@ B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */, B5C976E61C6E3A5900B1AF90 /* Internals.CoreStoreFetchedResultsController.swift */, B5474D142227C08700B21FEC /* Internals.CoreStoreFetchRequest.swift */, - B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */, B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */, + B51260921E9B28F100402229 /* Internals.EntityIdentifier.swift */, B5BF7FBB234C99190070E741 /* Internals.FallbackDiffableDataUIDispatcher.swift */, - B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */, B501322F2346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift */, + B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */, + B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */, B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */, B5E84F2B1AFF849C0064E85B /* Internals.NotificationObserver.swift */, + B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */, B5DE522A230BD7CC00A22534 /* Internals.swift */, B5E84F2D1AFF849C0064E85B /* Internals.WeakObject.swift */, B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */, @@ -1678,6 +1701,7 @@ B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */, B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */, B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */, + B5277671234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift */, B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */, B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */, B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */, @@ -2049,7 +2073,7 @@ B5831B751F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, B51B5C2D22D43E38009FA3BA /* KeyPath+KeyPaths.swift in Sources */, B5D8CA762346E7590055D7D1 /* DataStack+DataSources.swift in Sources */, - B5E5FA4E22D162F400330931 /* ObjectSnapshot.swift in Sources */, + B5BF7FC1234D7B2E0070E741 /* LiveObject.swift in Sources */, B5E1B5A81CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */, B50132302346B76E00FC238B /* Internals.FetchedDiffableDataSourceSnapshotDelegate.swift in Sources */, B5D339F11E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, @@ -2061,6 +2085,7 @@ B509C7F41E54511B0061C547 /* ImportableAttributeType.swift in Sources */, B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */, B5E1B5931CAA0C15007FD580 /* CSObjectMonitor.swift in Sources */, + B5277677234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */, B5ECDC291CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, B56923F01EB827F6007C4DC9 /* XcodeSchemaMappingProvider.swift in Sources */, B5F335582348D75D00FD649F /* LiveResult.swift in Sources */, @@ -2103,12 +2128,14 @@ B50EE14223473C92009B8C47 /* CoreStoreObject+DataSources.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */, + B5BF7FCB234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */, B596BBB61DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B5E1B5A21CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, B53304AA230BA4F7007C2BD8 /* DynamicObjectMeta.swift in Sources */, B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */, + B5BF7FC6234D7E460070E741 /* ObjectSnapshot.swift in Sources */, B5E84EF71AFF846E0064E85B /* UnsafeDataTransaction.swift in Sources */, B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */, B5D339DD1E9489C700C880DE /* DynamicObject.swift in Sources */, @@ -2170,6 +2197,7 @@ B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */, + B5277672234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, B5DBE2CD1C9914A900B5CEFA /* CSCoreStore.swift in Sources */, B546F95D1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B5D339E71E9493A500C880DE /* Entity.swift in Sources */, @@ -2236,11 +2264,13 @@ 82BA18CE1C4BBD7100A0916E /* Internals.FetchedResultsControllerDelegate.swift in Sources */, B56923FB1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */, B55514EB1EED8BF900BAB888 /* From+Querying.swift in Sources */, + B5BF7FC2234D7B2E0070E741 /* LiveObject.swift in Sources */, B596BBBC1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B5ECDC011CA80CBA00C7F112 /* CSWhere.swift in Sources */, B5ECDC071CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5E1B59A1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A601CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, + B5277678234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */, B52FD3AB1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74421E9B8724005F3DAC /* UnsafeDataModelSchema.swift in Sources */, B51FE5AD1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, @@ -2254,11 +2284,11 @@ 82BA18A51C4BBD2200A0916E /* CoreStore+Setup.swift in Sources */, 82BA18BD1C4BBD4A00A0916E /* GroupBy.swift in Sources */, B5ECDC1F1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, + B5BF7FCC234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */, B5E294DE2349F8E7003E5956 /* SnapshotResult.swift in Sources */, B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, B53FBA141CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, B5831B761F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, - B5E5FA4F22D162F400330931 /* ObjectSnapshot.swift in Sources */, B5BF7FB3234C97910070E741 /* DiffableDataSource.swift in Sources */, B5E1B5AA1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */, B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, @@ -2362,6 +2392,7 @@ 82BA18D31C4BBD7100A0916E /* NSManagedObjectContext+CoreStore.swift in Sources */, 82BA18AD1C4BBD3100A0916E /* UnsafeDataTransaction.swift in Sources */, B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, + B5277673234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */, 82BA18A81C4BBD2900A0916E /* CoreStoreLogger.swift in Sources */, B549F65F1E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */, @@ -2387,6 +2418,7 @@ B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B5ECDC0D1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5D339E81E9493A500C880DE /* Entity.swift in Sources */, + B5BF7FC7234D7E460070E741 /* ObjectSnapshot.swift in Sources */, 82BA18CC1C4BBD6400A0916E /* Progress+Convenience.swift in Sources */, B5BF7FB8234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */, 82BA18C01C4BBD5300A0916E /* DataStack+Observing.swift in Sources */, @@ -2448,11 +2480,13 @@ B52DD1951BE1F92500949AFE /* CoreStoreError.swift in Sources */, B56923FD1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */, B55514ED1EED8BF900BAB888 /* From+Querying.swift in Sources */, + B5BF7FC4234D7B2E0070E741 /* LiveObject.swift in Sources */, B596BBBE1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B546F9601C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B5ECDC0F1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5ECDC211CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B52DD1C21BE1F94600949AFE /* Internals.MigrationManager.swift in Sources */, + B527767A234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */, B52FD3AD1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74441E9B8724005F3DAC /* UnsafeDataModelSchema.swift in Sources */, B5ECDC2D1CA81CC700C7F112 /* CSDataStack+Transaction.swift in Sources */, @@ -2466,11 +2500,11 @@ B546F95B1C99B17400D5AC55 /* CSCoreStore+Setup.swift in Sources */, B52DD1A11BE1F92C00949AFE /* DataStack+Transaction.swift in Sources */, B5220E1C1D130801009BC71E /* Internals.FetchedResultsControllerDelegate.swift in Sources */, + B5BF7FCE234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */, B5E294E02349F8E7003E5956 /* SnapshotResult.swift in Sources */, B52DD19E1BE1F92C00949AFE /* AsynchronousDataTransaction.swift in Sources */, B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */, - B5E5FA5122D162F400330931 /* ObjectSnapshot.swift in Sources */, B5BF7FB5234C97910070E741 /* DiffableDataSource.swift in Sources */, B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */, @@ -2574,6 +2608,7 @@ B5ECDC331CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */, B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */, + B5277675234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */, B5220E151D130663009BC71E /* CoreStore+Observing.swift in Sources */, B549F6611E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */, @@ -2599,6 +2634,7 @@ B5AEFAB81C9962AE00AD137F /* CoreStoreBridge.swift in Sources */, B598514B1C90289F00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5D339EA1E9493A500C880DE /* Entity.swift in Sources */, + B5BF7FC9234D7E460070E741 /* ObjectSnapshot.swift in Sources */, B52DD1AA1BE1F93500949AFE /* TypeErasedClauses.swift in Sources */, B5BF7FBA234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */, B53FBA021CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */, @@ -2660,11 +2696,13 @@ B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */, B56923FC1EB82956007C4DC9 /* CSXcodeDataModelSchema.swift in Sources */, B55514EC1EED8BF900BAB888 /* From+Querying.swift in Sources */, + B5BF7FC3234D7B2E0070E741 /* LiveObject.swift in Sources */, B596BBBD1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B5ECDC081CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5E1B59B1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A611CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B5FE4DAE1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */, + B5277679234F265F0056BE9F /* Internals.SharedNotificationObserver.swift in Sources */, B52FD3AC1E3B3EF10001D919 /* NSManagedObject+Logging.swift in Sources */, B52F74431E9B8724005F3DAC /* UnsafeDataModelSchema.swift in Sources */, B51FE5AE1CD4D00300E54258 /* CoreStore+CustomDebugStringConvertible.swift in Sources */, @@ -2678,11 +2716,11 @@ B56321831BD65216006C9394 /* CoreStore+Setup.swift in Sources */, B5ECDC201CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, + B5BF7FCD234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */, B5E294DF2349F8E7003E5956 /* SnapshotResult.swift in Sources */, B53FBA151CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */, B5E1B5AB1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */, B5831B771F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */, - B5E5FA5022D162F400330931 /* ObjectSnapshot.swift in Sources */, B5BF7FB4234C97910070E741 /* DiffableDataSource.swift in Sources */, B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */, B5E1B5A01CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */, @@ -2786,6 +2824,7 @@ B56321B11BD6521C006C9394 /* NSManagedObjectContext+CoreStore.swift in Sources */, B563218D1BD65216006C9394 /* CoreStore+Transaction.swift in Sources */, B546F96B1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, + B5277674234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */, B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */, B563218B1BD65216006C9394 /* UnsafeDataTransaction.swift in Sources */, B549F6601E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */, @@ -2811,6 +2850,7 @@ B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B5ECDC0E1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5D339E91E9493A500C880DE /* Entity.swift in Sources */, + B5BF7FC8234D7E460070E741 /* ObjectSnapshot.swift in Sources */, B56321A41BD65216006C9394 /* CoreStore+Migration.swift in Sources */, B5BF7FB9234C97CE0070E741 /* DiffableDataSource.TableView.swift in Sources */, B56321A01BD65216006C9394 /* ObjectObserver.swift in Sources */, diff --git a/CoreStoreDemo/CoreStoreDemo/Info.plist b/CoreStoreDemo/CoreStoreDemo/Info.plist index 15874fa..c99470e 100644 --- a/CoreStoreDemo/CoreStoreDemo/Info.plist +++ b/CoreStoreDemo/CoreStoreDemo/Info.plist @@ -50,5 +50,7 @@ UIViewControllerBasedStatusBarAppearance + UIUserInterfaceStyle + light diff --git a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift index 107ead4..a2d73ac 100644 --- a/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift +++ b/CoreStoreDemo/CoreStoreDemo/SwiftUI Demo/SwiftUIView.swift @@ -19,30 +19,36 @@ struct SwiftUIView: View { @ObservedObject var palettes: LiveList - - @State - private var needsShowAlert = false + + @available(iOS 13.0.0, *) + struct ColorCell: View { + + @ObservedObject + var palette: LiveObject + + var body: some View { + HStack { + Color(palette.color) + .cornerRadius(5) + .frame(width: 30, height: 30, alignment: .leading) + Text(palette.colorText) + } + } + } var body: some View { NavigationView { List { - ForEach(palettes.snapshot.sectionIdentifiers, id: \.self) { (sectionID: String) in + ForEach(palettes.sections, id: \.self) { (sectionID) in Section(header: Text(sectionID)) { - ForEach(self.palettes.snapshot[section: sectionID], id: \.self) { palette in + ForEach(self.palettes[section: sectionID], id: \.self) { palette in NavigationLink( destination: DetailView(palette: palette), - label: { - HStack { - Color(palette.color) - .cornerRadius(5) - .frame(width: 30, height: 30, alignment: .leading) - Text(palette.colorText) - } - } + label: { ColorCell(palette: palette) } ) } .onDelete { itemIndices in - let objectsToDelete = self.palettes.snapshot[section: sectionID, itemIndices: itemIndices] + let objectsToDelete = self.palettes[section: sectionID, itemIndices: itemIndices].map({ $0.object }) self.dataStack.perform( asynchronous: { transaction in @@ -91,6 +97,9 @@ struct SwiftUIView: View { } .colorScheme(.dark) } + + @State + private var needsShowAlert = false } @available(iOS 13.0.0, *) @@ -98,19 +107,20 @@ struct DetailView: View { @Environment(\.dataStack) var dataStack: DataStack - - @ObservedObject var palette: Palette + + @ObservedObject + var palette: LiveObject @State var hue: Float = 0 @State var saturation: Float = 0 @State var brightness: Float = 0 - init(palette: Palette) { + init(palette: LiveObject) { self.palette = palette - self.hue = Float(palette.hue.value) - self.saturation = palette.saturation.value - self.brightness = palette.brightness.value + self.hue = Float(palette.hue) + self.saturation = palette.saturation + self.brightness = palette.brightness } var body: some View { diff --git a/Sources/DynamicObject.swift b/Sources/DynamicObject.swift index 0cf760d..d5054aa 100644 --- a/Sources/DynamicObject.swift +++ b/Sources/DynamicObject.swift @@ -43,7 +43,12 @@ public protocol DynamicObject: AnyObject { Used internally by CoreStore. Do not call directly. */ static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self - + + /** + Used internally by CoreStore. Do not call directly. + */ + static func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any] + /** Used internally by CoreStore. Do not call directly. */ @@ -97,6 +102,13 @@ extension NSManagedObject: DynamicObject { } return object } + + public class func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any] { + + let object = context.fetchExisting(id)! as Self + let rawObject = object.cs_toRaw() + return rawObject.dictionaryWithValues(forKeys: rawObject.entity.properties.map({ $0.name })) + } public class func cs_fromRaw(object: NSManagedObject) -> Self { @@ -119,16 +131,6 @@ extension NSManagedObject: DynamicObject { } } -extension DynamicObject where Self: NSManagedObject { - - // MARK: Public - - public func createSnapshot() -> ObjectSnapshot { - - return ObjectSnapshot(from: self) - } -} - // MARK: - CoreStoreObject @@ -146,6 +148,43 @@ extension CoreStoreObject { } return self.cs_fromRaw(object: object) } + + public class func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any] { + + func initializeAttributes(mirror: Mirror, object: Self, into attributes: inout [KeyPathString: Any]) { + + if let superClassMirror = mirror.superclassMirror { + + initializeAttributes( + mirror: superClassMirror, + object: object, + into: &attributes + ) + } + for child in mirror.children { + + switch child.value { + + case let property as AttributeProtocol: + attributes[property.keyPath] = property.valueForSnapshot + + case let property as RelationshipProtocol: + attributes[property.keyPath] = property.valueForSnapshot + + default: + continue + } + } + } + let object = context.fetchExisting(id)! as Self + var values: [KeyPathString: Any] = [:] + initializeAttributes( + mirror: Mirror(reflecting: object), + object: object, + into: &values + ) + return values + } public class func cs_fromRaw(object: NSManagedObject) -> Self { @@ -181,13 +220,3 @@ extension CoreStoreObject { return self.rawObject! } } - -extension DynamicObject where Self: CoreStoreObject { - - // MARK: Public - - public func createSnapshot() -> ObjectSnapshot { - - return ObjectSnapshot(from: self) - } -} diff --git a/Sources/Internals.DiffableDataSourceSnapshot.swift b/Sources/Internals.DiffableDataSourceSnapshot.swift index 65f8057..f5afcf9 100644 --- a/Sources/Internals.DiffableDataSourceSnapshot.swift +++ b/Sources/Internals.DiffableDataSourceSnapshot.swift @@ -40,88 +40,111 @@ import AppKit extension Internals { - // MARK: Internal - internal typealias DiffableDataSourceSnapshot = _Internal_DiffableDataSourceSnapshot - - - // MARK: - FallbackDiffableDataSourceSnapshot + // MARK: - DiffableDataSourceSnapshot // Implementation based on https://github.com/ra1028/DiffableDataSources - internal struct FallbackDiffableDataSourceSnapshot: DiffableDataSourceSnapshot { + internal struct DiffableDataSourceSnapshot { // MARK: Internal - init(sections: [NSFetchedResultsSectionInfo]) { - - self.structure = .init(sections: sections) - } - - // MARK: DiffableDataSourceSnapshot + internal let nextStateTag: UUID init() { self.structure = .init() + self.nextStateTag = .init() + } + + init(sections: [NSFetchedResultsSectionInfo], previousStateTag: UUID, nextStateTag: UUID) { + + self.structure = .init(sections: sections, previousStateTag: previousStateTag) + self.nextStateTag = nextStateTag } var numberOfItems: Int { - return self.itemIdentifiers.count + return self.structure.allItemIDs.count } var numberOfSections: Int { - return self.sectionIdentifiers.count + return self.structure.allSectionIDs.count } - var sectionIdentifiers: [NSString] { + var allSectionIDs: [String] { return self.structure.allSectionIDs } - var itemIdentifiers: [NSManagedObjectID] { + var allSectionStateIDs: [SectionStateID] { - self.structure.allItemIDs + return self.structure.allSectionStateIDs } - func numberOfItems(inSection identifier: NSString) -> Int { + var allItemIDs: [NSManagedObjectID] { - return self.itemIdentifiers(inSection: identifier).count + return self.structure.allItemIDs } - func itemIdentifiers(inSection identifier: NSString) -> [NSManagedObjectID] { + var allItemStateIDs: [ItemStateID] { + + return self.structure.allItemStateIDs + } + + func numberOfItems(inSection identifier: String) -> Int { + + return self.itemIDs(inSection: identifier).count + } + + func itemIDs(inSection identifier: String) -> [NSManagedObjectID] { return self.structure.items(in: identifier) } - func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> NSString? { + func itemStateIDs(inSection identifier: String) -> [ItemStateID] { + + return self.structure.itemStateIDs(in: identifier) + } + + func sectionIDs(containingItem identifier: NSManagedObjectID) -> String? { return self.structure.section(containing: identifier) } - func indexOfItem(_ identifier: NSManagedObjectID) -> Int? { + func sectionStateIDs(containingItem identifier: NSManagedObjectID) -> SectionStateID? { - return self.itemIdentifiers.firstIndex(of: identifier) + return self.structure.sectionStateID(containing: identifier) } - func indexOfSection(_ identifier: NSString) -> Int? { + func indexOfItemID(_ identifier: NSManagedObjectID) -> Int? { - return self.sectionIdentifiers.firstIndex(of: identifier) + return self.structure.allItemIDs.firstIndex(of: identifier) } - mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: NSString?) { + func indexOfSectionID(_ identifier: String) -> Int? { - self.structure.append(itemIDs: identifiers, to: sectionIdentifier) + return self.structure.allSectionIDs.firstIndex(of: identifier) } - mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID) { + func itemIDs(where stateCondition: @escaping (UUID) -> Bool) -> [NSManagedObjectID] { - self.structure.insert(itemIDs: identifiers, before: beforeIdentifier) + return self.structure.itemsIDs(where: stateCondition) } - mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID) { + mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?, nextStateTag: UUID) { - self.structure.insert(itemIDs: identifiers, after: afterIdentifier) + self.structure.append(itemIDs: identifiers, to: sectionIdentifier, nextStateTag: nextStateTag) + } + + mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID, nextStateTag: UUID) { + + self.structure.insert(itemIDs: identifiers, before: beforeIdentifier, nextStateTag: nextStateTag) + } + + mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID, nextStateTag: UUID) { + + self.structure.insert(itemIDs: identifiers, after: afterIdentifier, nextStateTag: nextStateTag) } mutating func deleteItems(_ identifiers: [NSManagedObjectID]) { @@ -144,44 +167,44 @@ extension Internals { self.structure.move(itemID: identifier, after: toIdentifier) } - mutating func reloadItems(_ identifiers: [NSManagedObjectID]) { + mutating func reloadItems(_ identifiers: S, nextStateTag: UUID) where S.Element == NSManagedObjectID { - self.structure.update(itemIDs: identifiers) + self.structure.update(itemIDs: identifiers, nextStateTag: nextStateTag) } - mutating func appendSections(_ identifiers: [NSString]) { + mutating func appendSections(_ identifiers: [String], nextStateTag: UUID) { - self.structure.append(sectionIDs: identifiers) + self.structure.append(sectionIDs: identifiers, nextStateTag: nextStateTag) } - mutating func insertSections(_ identifiers: [NSString], beforeSection toIdentifier: NSString) { + mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String, nextStateTag: UUID) { - self.structure.insert(sectionIDs: identifiers, before: toIdentifier) + self.structure.insert(sectionIDs: identifiers, before: toIdentifier, nextStateTag: nextStateTag) } - mutating func insertSections(_ identifiers: [NSString], afterSection toIdentifier: NSString) { + mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String, nextStateTag: UUID) { - self.structure.insert(sectionIDs: identifiers, after: toIdentifier) + self.structure.insert(sectionIDs: identifiers, after: toIdentifier, nextStateTag: nextStateTag) } - mutating func deleteSections(_ identifiers: [NSString]) { + mutating func deleteSections(_ identifiers: [String]) { self.structure.remove(sectionIDs: identifiers) } - mutating func moveSection(_ identifier: NSString, beforeSection toIdentifier: NSString) { + mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) { self.structure.move(sectionID: identifier, before: toIdentifier) } - mutating func moveSection(_ identifier: NSString, afterSection toIdentifier: NSString) { + mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) { self.structure.move(sectionID: identifier, after: toIdentifier) } - mutating func reloadSections(_ identifiers: [NSString]) { + mutating func reloadSections(_ identifiers: S, nextStateTag: UUID) where S.Element == String { - self.structure.update(sectionIDs: identifiers) + self.structure.update(sectionIDs: identifiers, nextStateTag: nextStateTag) } @@ -190,9 +213,54 @@ extension Internals { private var structure: BackingStructure + // MARK: - ItemStateID + + internal struct ItemStateID: Identifiable, Equatable { + + let stateTag: UUID + + init(id: NSManagedObjectID, stateTag: UUID) { + + self.id = id + self.stateTag = stateTag + } + + func isContentEqual(to source: ItemStateID) -> Bool { + + return self.id == source.id && self.stateTag == source.stateTag + } + + // MARK: Identifiable + + let id: NSManagedObjectID + } + + + // MARK: - SectionStateID + + internal struct SectionStateID: Identifiable, Equatable { + + let stateTag: UUID + + init(id: String, stateTag: UUID) { + self.id = id + self.stateTag = stateTag + } + + func isContentEqual(to source: SectionStateID) -> Bool { + + return self.id == source.id && self.stateTag == source.stateTag + } + + // MARK: Identifiable + + let id: String + } + + // MARK: - BackingStructure - internal struct BackingStructure { + fileprivate struct BackingStructure { // MARK: Internal @@ -203,31 +271,41 @@ extension Internals { self.sections = [] } - init(sections: [NSFetchedResultsSectionInfo]) { + init(sections: [NSFetchedResultsSectionInfo], previousStateTag: UUID) { self.sections = sections.map { Section( - id: $0.name as NSString, + id: $0.name, items: $0.objects? .compactMap({ ($0 as? NSManagedObject)?.objectID }) - .map(Item.init(id:)) ?? [], - isReloaded: false + .map({ Item(id: $0, stateTag: previousStateTag) }) ?? [], + stateTag: previousStateTag ) } } - var allSectionIDs: [NSString] { + var allSectionIDs: [String] { return self.sections.map({ $0.id }) } + var allSectionStateIDs: [SectionStateID] { + + return self.sections.map({ $0.stateID }) + } + var allItemIDs: [NSManagedObjectID] { return self.sections.lazy.flatMap({ $0.elements }).map({ $0.id }) } - func items(in sectionID: NSString) -> [NSManagedObjectID] { + var allItemStateIDs: [ItemStateID] { + + return self.sections.lazy.flatMap({ $0.elements }).map({ $0.stateID }) + } + + func items(in sectionID: String) -> [NSManagedObjectID] { guard let sectionIndex = self.sectionIndex(of: sectionID) else { @@ -236,12 +314,33 @@ extension Internals { return self.sections[sectionIndex].elements.map({ $0.id }) } - func section(containing itemID: NSManagedObjectID) -> NSString? { + func itemsIDs(where stateCondition: @escaping (UUID) -> Bool) -> [NSManagedObjectID] { + + return self.sections.lazy + .flatMap({ $0.elements.filter({ stateCondition($0.stateTag) }) }) + .map({ $0.id }) + } + + func itemStateIDs(in sectionID: String) -> [ItemStateID] { + + guard let sectionIndex = self.sectionIndex(of: sectionID) else { + + Internals.abort("Section \"\(sectionID)\" does not exist") + } + return self.sections[sectionIndex].elements.map({ $0.stateID }) + } + + func section(containing itemID: NSManagedObjectID) -> String? { return self.itemPositionMap()[itemID]?.section.id } - mutating func append(itemIDs: [NSManagedObjectID], to sectionID: NSString?) { + func sectionStateID(containing itemID: NSManagedObjectID) -> SectionStateID? { + + return self.itemPositionMap()[itemID]?.section.stateID + } + + mutating func append(itemIDs: [NSManagedObjectID], to sectionID: String?, nextStateTag: UUID) { let index: Array
.Index if let sectionID = sectionID { @@ -261,23 +360,22 @@ extension Internals { } index = section.index(before: section.endIndex) } - - let items = itemIDs.lazy.map(Item.init) + let items = itemIDs.lazy.map({ Item(id: $0, stateTag: nextStateTag) }) self.sections[index].elements.append(contentsOf: items) } - mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID) { + mutating func insert(itemIDs: [NSManagedObjectID], before beforeItemID: NSManagedObjectID, nextStateTag: UUID) { guard let itemPosition = self.itemPositionMap()[beforeItemID] else { Internals.abort("Item \(beforeItemID) does not exist") } - let items = itemIDs.lazy.map(Item.init) + let items = itemIDs.lazy.map({ Item(id: $0, stateTag: nextStateTag) }) self.sections[itemPosition.sectionIndex].elements .insert(contentsOf: items, at: itemPosition.itemRelativeIndex) } - mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: NSManagedObjectID) { + mutating func insert(itemIDs: [NSManagedObjectID], after afterItemID: NSManagedObjectID, nextStateTag: UUID) { guard let itemPosition = self.itemPositionMap()[afterItemID] else { @@ -285,7 +383,7 @@ extension Internals { } let itemIndex = self.sections[itemPosition.sectionIndex].elements .index(after: itemPosition.itemRelativeIndex) - let items = itemIDs.lazy.map(Item.init) + let items = itemIDs.lazy.map({ Item(id: $0, stateTag: nextStateTag) }) self.sections[itemPosition.sectionIndex].elements .insert(contentsOf: items, at: itemIndex) } @@ -351,47 +449,48 @@ extension Internals { .insert(removed, at: itemIndex) } - mutating func update(itemIDs: [NSManagedObjectID]) { + mutating func update(itemIDs: S, nextStateTag: UUID) where S.Element == NSManagedObjectID { let itemPositionMap = self.itemPositionMap() for itemID in itemIDs { guard let itemPosition = itemPositionMap[itemID] else { - Internals.abort("Item \(itemID) does not exist") + continue } - self.sections[itemPosition.sectionIndex].elements[itemPosition.itemRelativeIndex].isReloaded = true + self.sections[itemPosition.sectionIndex] + .elements[itemPosition.itemRelativeIndex].stateTag = nextStateTag } } - mutating func append(sectionIDs: [NSString]) { + mutating func append(sectionIDs: [String], nextStateTag: UUID) { - let newSections = sectionIDs.lazy.map(Section.init) + let newSections = sectionIDs.lazy.map({ Section(id: $0, stateTag: nextStateTag) }) self.sections.append(contentsOf: newSections) } - mutating func insert(sectionIDs: [NSString], before beforeSectionID: NSString) { + mutating func insert(sectionIDs: [String], before beforeSectionID: String, nextStateTag: UUID) { guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else { Internals.abort("Section \"\(beforeSectionID)\" does not exist") } - let newSections = sectionIDs.lazy.map(Section.init) + let newSections = sectionIDs.lazy.map({ Section(id: $0, stateTag: nextStateTag) }) self.sections.insert(contentsOf: newSections, at: sectionIndex) } - mutating func insert(sectionIDs: [NSString], after afterSectionID: NSString) { + mutating func insert(sectionIDs: [String], after afterSectionID: String, nextStateTag: UUID) { guard let beforeIndex = self.sectionIndex(of: afterSectionID) else { Internals.abort("Section \"\(afterSectionID)\" does not exist") } let sectionIndex = self.sections.index(after: beforeIndex) - let newSections = sectionIDs.lazy.map(Section.init) + let newSections = sectionIDs.lazy.map({ Section(id: $0, stateTag: nextStateTag) }) self.sections.insert(contentsOf: newSections, at: sectionIndex) } - mutating func remove(sectionIDs: [NSString]) { + mutating func remove(sectionIDs: [String]) { for sectionID in sectionIDs { @@ -399,7 +498,7 @@ extension Internals { } } - mutating func move(sectionID: NSString, before beforeSectionID: NSString) { + mutating func move(sectionID: String, before beforeSectionID: String) { guard let removed = self.remove(sectionID: sectionID) else { @@ -412,7 +511,7 @@ extension Internals { self.sections.insert(removed, at: sectionIndex) } - mutating func move(sectionID: NSString, after afterSectionID: NSString) { + mutating func move(sectionID: String, after afterSectionID: String) { guard let removed = self.remove(sectionID: sectionID) else { @@ -426,7 +525,7 @@ extension Internals { self.sections.insert(removed, at: sectionIndex) } - mutating func update(sectionIDs: [NSString]) { + mutating func update(sectionIDs: S, nextStateTag: UUID) where S.Element == String { for sectionID in sectionIDs { @@ -434,14 +533,16 @@ extension Internals { continue } - self.sections[sectionIndex].isReloaded = true + self.sections[sectionIndex].stateTag = nextStateTag } } // MARK: Private - private func sectionIndex(of sectionID: NSString) -> Array
.Index? { + private static let zeroUUID: UUID = .init(uuid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + + private func sectionIndex(of sectionID: String) -> Array
.Index? { return self.sections.firstIndex(where: { $0.id == sectionID }) } @@ -458,7 +559,7 @@ extension Internals { } @discardableResult - private mutating func remove(sectionID: NSString) -> Section? { + private mutating func remove(sectionID: String) -> Section? { guard let sectionIndex = self.sectionIndex(of: sectionID) else { @@ -486,63 +587,63 @@ extension Internals { // MARK: - Item - internal struct Item: Identifiable, Equatable { + fileprivate struct Item: Identifiable, Equatable { - var isReloaded: Bool + var stateTag: UUID - init(id: NSManagedObjectID, isReloaded: Bool) { + init(id: NSManagedObjectID, stateTag: UUID) { self.id = id - self.isReloaded = isReloaded + self.stateTag = stateTag } - init(id: NSManagedObjectID) { + var stateID: ItemStateID { - self.init(id: id, isReloaded: false) + return .init(id: self.id, stateTag: self.stateTag) } func isContentEqual(to source: Item) -> Bool { - return !self.isReloaded && self.id == source.id + return self.id == source.id && self.stateTag == source.stateTag } // MARK: Identifiable - var id: NSManagedObjectID + let id: NSManagedObjectID } // MARK: - Section - internal struct Section: Identifiable, Equatable { + fileprivate struct Section: Identifiable, Equatable { var elements: [Item] = [] - var isReloaded: Bool + var stateTag: UUID - init(id: NSString, items: [Item], isReloaded: Bool) { + init(id: String, items: [Item] = [], stateTag: UUID) { self.id = id self.elements = items - self.isReloaded = isReloaded - } - - init(id: NSString) { - - self.init(id: id, items: [], isReloaded: false) + self.stateTag = stateTag } init(source: Section, elements: S) where S.Element == Item { - self.init(id: source.id, items: Array(elements), isReloaded: source.isReloaded) + self.init(id: source.id, items: Array(elements), stateTag: source.stateTag) + } + + var stateID: SectionStateID { + + return .init(id: self.id, stateTag: self.stateTag) } func isContentEqual(to source: Section) -> Bool { - return !self.isReloaded && self.id == source.id + return self.id == source.id && self.stateTag == source.stateTag } // MARK: Identifiable - var id: NSString + let id: String } @@ -550,57 +651,14 @@ extension Internals { fileprivate struct ItemPosition { - var item: Item - var itemRelativeIndex: Int - var section: Section - var sectionIndex: Int + let item: Item + let itemRelativeIndex: Int + let section: Section + let sectionIndex: Int } } } } -// MARK: - NSDiffableDataSourceSnapshot: Internals.DiffableDataSourceSnapshot - -@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) -extension NSDiffableDataSourceSnapshot: Internals.DiffableDataSourceSnapshot where SectionIdentifierType == NSString, ItemIdentifierType == NSManagedObjectID {} - - -// MARK: - Internals.DiffableDataSourceSnapshot - -internal protocol _Internal_DiffableDataSourceSnapshot { - - init() - - var numberOfItems: Int { get } - var numberOfSections: Int { get } - var sectionIdentifiers: [NSString] { get } - var itemIdentifiers: [NSManagedObjectID] { get } - - func numberOfItems(inSection identifier: NSString) -> Int - func itemIdentifiers(inSection identifier: NSString) -> [NSManagedObjectID] - func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> NSString? - func indexOfItem(_ identifier: NSManagedObjectID) -> Int? - func indexOfSection(_ identifier: NSString) -> Int? - - mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: NSString?) - mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID) - mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID) - mutating func deleteItems(_ identifiers: [NSManagedObjectID]) - mutating func deleteAllItems() - mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID) - mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID) - mutating func reloadItems(_ identifiers: [NSManagedObjectID]) - mutating func appendSections(_ identifiers: [NSString]) - mutating func insertSections(_ identifiers: [NSString], beforeSection toIdentifier: NSString) - mutating func insertSections(_ identifiers: [NSString], afterSection toIdentifier: NSString) - mutating func deleteSections(_ identifiers: [NSString]) - mutating func moveSection(_ identifier: NSString, beforeSection toIdentifier: NSString) - mutating func moveSection(_ identifier: NSString, afterSection toIdentifier: NSString) - mutating func reloadSections(_ identifiers: [NSString]) -} - - - - #endif diff --git a/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift b/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift index 92f8f92..a9185f8 100644 --- a/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift +++ b/Sources/Internals.FetchedDiffableDataSourceSnapshotDelegate.swift @@ -75,14 +75,14 @@ extension Internals { internal func initialFetch() { - #if canImport(UIKit) || canImport(AppKit) - - if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) { - - return - } - - #endif +// #if canImport(UIKit) || canImport(AppKit) +// +// if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) { +// +// return +// } +// +// #endif guard let fetchedResultsController = self.fetchedResultsController else { @@ -94,50 +94,65 @@ extension Internals { // MARK: NSFetchedResultsControllerDelegate - #if canImport(UIKit) || canImport(AppKit) - - @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) - @objc - dynamic func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { - - self.handler?.controller( - controller, - didChangContentWith: snapshot as NSDiffableDataSourceSnapshot - ) - } - - #endif - - @objc - dynamic func controllerWillChangeContent(_ controller: NSFetchedResultsController) { - - self.reloadedIDs = [] - } +// #if canImport(UIKit) || canImport(AppKit) +// +// @available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) +// @objc +// dynamic func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { +// +// self.handler?.controller( +// controller, +// didChangContentWith: snapshot as NSDiffableDataSourceSnapshot +// ) +// } +// +// #endif @objc dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + let nextStateTag: UUID = .init() + var dataSourceSnapshot = Internals.DiffableDataSourceSnapshot( + sections: controller.sections ?? [], + previousStateTag: self.previousStateTag, + nextStateTag: nextStateTag + ) + dataSourceSnapshot.reloadItems(self.reloadedItemIDs, nextStateTag: nextStateTag) + dataSourceSnapshot.reloadSections(self.reloadedSectionIDs, nextStateTag: nextStateTag) + self.handler?.controller( controller, - didChangContentWith: FallbackDiffableDataSourceSnapshot( - sections: controller.sections ?? [] - ) + didChangContentWith: dataSourceSnapshot ) - self.reloadedIDs = [] + self.previousStateTag = nextStateTag } -// @objc -// dynamic func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { -// -// -//// [1].difference(from: <#T##BidirectionalCollection#>) -// let managedObject = anObject as! NSManagedObject -// self.reloadedIDs.insert(managedObject.objectID) -// } + @objc + dynamic func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { + + let managedObject = anObject as! NSManagedObject + self.reloadedItemIDs.insert(managedObject.objectID) + } + + @objc + dynamic func controller(_ controller: NSFetchedResultsController, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) { + + self.reloadedSectionIDs.insert(sectionInfo.name) + } // MARK: Private - private var reloadedIDs: Set = [] + private var previousStateTag: UUID = .init() { + + didSet { + + self.reloadedItemIDs = [] + self.reloadedSectionIDs = [] + } + } + + private var reloadedItemIDs: Set = [] + private var reloadedSectionIDs: Set = [] } } diff --git a/Sources/Internals.LazyNonmutating.swift b/Sources/Internals.LazyNonmutating.swift new file mode 100644 index 0000000..4557bcd --- /dev/null +++ b/Sources/Internals.LazyNonmutating.swift @@ -0,0 +1,94 @@ +// +// Internals.LazyNonmutating.swift +// CoreStore +// +// Copyright © 2018 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +// MARK: - Internals + +extension Internals { + + // MARK: - LazyNonmutating + + @propertyWrapper + internal final class LazyNonmutating { + + // MARK: Internal + + init(_ initializer: @escaping () -> Value) { + + self.initializer = initializer + } + + init(uninitialized: Void) { + + self.initializer = { fatalError() } + } + + func initialize(_ initializer: @escaping () -> Value) { + + self.initializer = initializer + } + + func reset(_ initializer: @escaping () -> Value) { + + self.initializer = initializer + self.initializedValue = nil + } + + + // MARK: @propertyWrapper + + var wrappedValue: Value { + + get { + + if let initializedValue = self.initializedValue { + + return initializedValue + } + let initializedValue = self.initializer() + self.initializedValue = initializedValue + return initializedValue + } + set { + + self.initializer = { newValue } + self.initializedValue = newValue + } + } + + var projectedValue: Internals.LazyNonmutating { + + return self + } + + + // MARK: Private + + private var initializer: () -> Value + private var initializedValue: Value? = nil + } +} diff --git a/Sources/Internals.NotificationObserver.swift b/Sources/Internals.NotificationObserver.swift index 26faf20..014a72e 100644 --- a/Sources/Internals.NotificationObserver.swift +++ b/Sources/Internals.NotificationObserver.swift @@ -34,7 +34,7 @@ extension Internals { internal final class NotificationObserver { - // MARK: Public + // MARK: Internal let observer: NSObjectProtocol diff --git a/Sources/Internals.SharedNotificationObserver.swift b/Sources/Internals.SharedNotificationObserver.swift new file mode 100644 index 0000000..c97aa4c --- /dev/null +++ b/Sources/Internals.SharedNotificationObserver.swift @@ -0,0 +1,56 @@ +// +// Internals.SharedNotificationObserver.swift +// CoreStore +// +// Copyright © 2018 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +// MARK: - Internal + +extension Internals { + + // MARK: - SharedNotificationObserver + + internal final class SharedNotificationObserver { + + // MARK: Internal + + let observer: NSObjectProtocol + + init(notificationName: Notification.Name, object: Any?, queue: OperationQueue? = nil, closure: @escaping (_ note: Notification) -> Void) { + + self.observer = NotificationCenter.default.addObserver( + forName: notificationName, + object: object, + queue: queue, + using: closure + ) + } + + deinit { + + NotificationCenter.default.removeObserver(self.observer) + } + } +} diff --git a/Sources/ListSnapshot.swift b/Sources/ListSnapshot.swift index dba82f5..5509a4b 100644 --- a/Sources/ListSnapshot.swift +++ b/Sources/ListSnapshot.swift @@ -34,44 +34,44 @@ import AppKit #endif -// MARK: - LiveList +// MARK: - ListSnapshot -public struct ListSnapshot: SnapshotResult, RandomAccessCollection, Hashable { +public struct ListSnapshot: SnapshotResult, RandomAccessCollection, Hashable { // MARK: Public public typealias SectionID = String - public typealias ItemID = D.ObjectID + public typealias ItemID = O.ObjectID - public subscript(indices indices: S) -> [ObjectType] where S.Element == Index { + public subscript(indices indices: S) -> [LiveObject] where S.Element == Index { let context = self.context! - let objectIDs = self.diffableSnapshot.itemIdentifiers + let itemIDs = self.diffableSnapshot.allItemIDs return indices.map { position in - let objectID = objectIDs[position] - return context.fetchExisting(objectID)! + let itemID = itemIDs[position] + return LiveObject(id: itemID, context: context) } } - public subscript(section sectionID: SectionID) -> [ObjectType] { + public subscript(section sectionID: SectionID) -> [LiveObject] { let context = self.context! - let objectIDs = self.itemIdentifiers(inSection: sectionID) - return objectIDs.map { - - return context.fetchExisting($0)! + let itemIDs = self.diffableSnapshot.itemIDs(inSection: sectionID) + return itemIDs.map { + + return LiveObject(id: $0, context: context) } } - public subscript(section sectionID: SectionID, itemIndices itemIndices: S) -> [ObjectType] where S.Element == Int { + public subscript(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject] where S.Element == Int { let context = self.context! - let objectIDs = self.itemIdentifiers(inSection: sectionID) + let itemIDs = self.diffableSnapshot.itemIDs(inSection: sectionID) return itemIndices.map { position in - let objectID = objectIDs[position] - return context.fetchExisting(objectID)! + let itemID = itemIDs[position] + return LiveObject(id: itemID, context: context) } } @@ -85,60 +85,60 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec return self.diffableSnapshot.numberOfSections } - public var sectionIdentifiers: [String] { + public var sectionIDs: [SectionID] { - return self.diffableSnapshot.sectionIdentifiers as [String] + return self.diffableSnapshot.allSectionIDs } public var itemIdentifiers: [ItemID] { - return self.diffableSnapshot.itemIdentifiers as [ItemID] + return self.diffableSnapshot.allItemIDs } public func numberOfItems(inSection identifier: SectionID) -> Int { - return self.diffableSnapshot.numberOfItems(inSection: identifier as NSString) + return self.diffableSnapshot.numberOfItems(inSection: identifier) } public func itemIdentifiers(inSection identifier: SectionID) -> [ItemID] { - return self.diffableSnapshot.itemIdentifiers(inSection: identifier as NSString) + return self.diffableSnapshot.itemIDs(inSection: identifier) } public func itemIdentifiers(inSection identifier: SectionID, atIndices indices: IndexSet) -> [ItemID] { - let itemIDs = self.itemIdentifiers(inSection: identifier) + let itemIDs = self.diffableSnapshot.itemIDs(inSection: identifier) return indices.map({ itemIDs[$0] }) } public func sectionIdentifier(containingItem identifier: ItemID) -> SectionID? { - return self.diffableSnapshot.sectionIdentifier(containingItem: identifier) as SectionID? + return self.diffableSnapshot.sectionIDs(containingItem: identifier) } public func indexOfItem(_ identifier: ItemID) -> Index? { - return self.diffableSnapshot.indexOfItem(identifier) + return self.diffableSnapshot.indexOfItemID(identifier) } public func indexOfSection(_ identifier: SectionID) -> Int? { - return self.diffableSnapshot.indexOfSection(identifier as NSString) + return self.diffableSnapshot.indexOfSectionID(identifier) } public mutating func appendItems(_ identifiers: [ItemID], toSection sectionIdentifier: SectionID? = nil) { - self.diffableSnapshot.appendItems(identifiers, toSection: sectionIdentifier as NSString?) + self.diffableSnapshot.appendItems(identifiers, toSection: sectionIdentifier, nextStateTag: .init()) } public mutating func insertItems(_ identifiers: [ItemID], beforeItem beforeIdentifier: ItemID) { - self.diffableSnapshot.insertItems(identifiers, beforeItem: beforeIdentifier) + self.diffableSnapshot.insertItems(identifiers, beforeItem: beforeIdentifier, nextStateTag: .init()) } public mutating func insertItems(_ identifiers: [ItemID], afterItem afterIdentifier: ItemID) { - self.diffableSnapshot.insertItems(identifiers, afterItem: afterIdentifier) + self.diffableSnapshot.insertItems(identifiers, afterItem: afterIdentifier, nextStateTag: .init()) } public mutating func deleteItems(_ identifiers: [ItemID]) { @@ -163,73 +163,73 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec public mutating func reloadItems(_ identifiers: [ItemID]) { - self.diffableSnapshot.reloadItems(identifiers) + self.diffableSnapshot.reloadItems(identifiers, nextStateTag: .init()) } public mutating func appendSections(_ identifiers: [SectionID]) { - self.diffableSnapshot.appendSections(identifiers as [NSString]) + self.diffableSnapshot.appendSections(identifiers, nextStateTag: .init()) } public mutating func insertSections(_ identifiers: [SectionID], beforeSection toIdentifier: SectionID) { - self.diffableSnapshot.insertSections(identifiers as [NSString], beforeSection: toIdentifier as NSString) + self.diffableSnapshot.insertSections(identifiers, beforeSection: toIdentifier, nextStateTag: .init()) } public mutating func insertSections(_ identifiers: [SectionID], afterSection toIdentifier: SectionID) { - self.diffableSnapshot.insertSections(identifiers as [NSString], afterSection: toIdentifier as NSString) + self.diffableSnapshot.insertSections(identifiers, afterSection: toIdentifier, nextStateTag: .init()) } public mutating func deleteSections(_ identifiers: [SectionID]) { - self.diffableSnapshot.deleteSections(identifiers as [NSString]) + self.diffableSnapshot.deleteSections(identifiers) } public mutating func moveSection(_ identifier: SectionID, beforeSection toIdentifier: SectionID) { - self.diffableSnapshot.moveSection(identifier as NSString, beforeSection: toIdentifier as NSString) + self.diffableSnapshot.moveSection(identifier, beforeSection: toIdentifier) } public mutating func moveSection(_ identifier: SectionID, afterSection toIdentifier: SectionID) { - self.diffableSnapshot.moveSection(identifier as NSString, afterSection: toIdentifier as NSString) + self.diffableSnapshot.moveSection(identifier, afterSection: toIdentifier) } public mutating func reloadSections(_ identifiers: [SectionID]) { - self.diffableSnapshot.reloadSections(identifiers as [NSString]) + self.diffableSnapshot.reloadSections(identifiers, nextStateTag: .init()) } // MARK: SnapshotResult - public typealias ObjectType = D + public typealias ObjectType = O // MARK: RandomAccessCollection public var startIndex: Index { - return self.diffableSnapshot.itemIdentifiers.startIndex + return self.diffableSnapshot.allItemIDs.startIndex } public var endIndex: Index { - return self.diffableSnapshot.itemIdentifiers.endIndex + return self.diffableSnapshot.allItemIDs.endIndex } - public subscript(position: Index) -> ObjectType { + public subscript(position: Index) -> Element { let context = self.context! - let objectID = self.diffableSnapshot.itemIdentifiers[position] - return context.fetchExisting(objectID)! + let itemID = self.diffableSnapshot.allItemIDs[position] + return LiveObject(id: itemID, context: context) } // MARK: Sequence - public typealias Element = ObjectType + public typealias Element = LiveObject public typealias Index = Int @@ -254,7 +254,7 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec internal init() { - self.diffableSnapshot = Internals.FallbackDiffableDataSourceSnapshot() + self.diffableSnapshot = .init() self.context = nil } @@ -263,11 +263,22 @@ public struct ListSnapshot: SnapshotResult, RandomAccessCollec self.diffableSnapshot = diffableSnapshot self.context = context } + + internal var nextStateTag: UUID { + + return self.diffableSnapshot.nextStateTag + } + + internal func itemIDs(where stateCondition: @escaping (UUID) -> Bool) -> [ItemID] { + + return self.diffableSnapshot.itemIDs(where: stateCondition) + } // MARK: Private private let id: UUID = .init() private let context: NSManagedObjectContext? + private var diffableSnapshot: Internals.DiffableDataSourceSnapshot } diff --git a/Sources/LiveList.swift b/Sources/LiveList.swift index e6a0cf8..1029a64 100644 --- a/Sources/LiveList.swift +++ b/Sources/LiveList.swift @@ -38,40 +38,135 @@ import SwiftUI // MARK: - LiveList -public final class LiveList { +public final class LiveList: Hashable { // MARK: Public - public fileprivate(set) var snapshot: ListSnapshot = .init() { + public typealias SectionID = SnapshotType.SectionID + public typealias ItemID = SnapshotType.ItemID + + public fileprivate(set) var snapshot: SnapshotType = .init() { willSet { - - guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) else { - return + if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) { + + self.willChange() } - #if canImport(Combine) - - #if canImport(SwiftUI) - withAnimation { - self.objectWillChange.send() - } - - #else - self.objectWillChange.send() - - #endif - - #endif } } + + public var numberOfItems: Int { + + return self.snapshot.numberOfItems + } + + public var numberOfSections: Int { + + return self.snapshot.numberOfSections + } + + public var sections: [SectionID] { + + return self.snapshot.sectionIDs + } + + public subscript(section sectionID: SectionID) -> [LiveObject] { + + let context = self.context + return self.snapshot + .itemIdentifiers(inSection: sectionID) + .map({ context.liveObject(id: $0) }) + } + + public subscript(itemID itemID: ItemID) -> LiveObject? { + + guard let validID = self.snapshot.itemIdentifiers.first(where: { $0 == itemID }) else { + + return nil + } + return self.context.liveObject(id: validID) + } + + public subscript(section sectionID: SectionID, itemIndices itemIndices: S) -> [LiveObject] where S.Element == Int { + + let context = self.context + let itemIDs = self.snapshot.itemIdentifiers(inSection: sectionID) + return itemIndices.map { position in + + let itemID = itemIDs[position] + return context.liveObject(id: itemID) + } + } + + public var items: [LiveObject] { + + let context = self.context + return self.snapshot.itemIdentifiers + .map({ context.liveObject(id: $0) }) + } + + public func numberOfItems(inSection identifier: SectionID) -> Int { + + return self.snapshot.numberOfItems(inSection: identifier) + } + + public func items(inSection identifier: SectionID) -> [LiveObject] { + + let context = self.context + return self.snapshot + .itemIdentifiers(inSection: identifier) + .map({ context.liveObject(id: $0) }) + } + + public func items(inSection identifier: SectionID, atIndices indices: IndexSet) -> [LiveObject] { + + let context = self.context + let itemIDs = self.snapshot.itemIdentifiers(inSection: identifier) + return indices.map { position in + + let itemID = itemIDs[position] + return context.liveObject(id: itemID) + } + } + + public func section(containingItem item: LiveObject) -> SectionID? { + + return self.snapshot.sectionIdentifier(containingItem: item.id) + } + + public func indexOfItem(_ item: LiveObject) -> Int? { + + return self.snapshot.indexOfItem(item.id) + } + + public func indexOfSection(_ identifier: SectionID) -> Int? { + + return self.snapshot.indexOfSection(identifier) + } + + + // MARK: Equatable + + public static func == (_ lhs: LiveList, _ rhs: LiveList) -> Bool { + + return lhs === rhs + } + + + // MARK: Hashable + + public func hash(into hasher: inout Hasher) { + + hasher.combine(ObjectIdentifier(self)) + } // MARK: LiveResult - public typealias ObjectType = D + public typealias ObjectType = O - public typealias SnapshotType = ListSnapshot + public typealias SnapshotType = ListSnapshot // MARK: Internal @@ -137,6 +232,8 @@ public final class LiveList { private let from: From private let sectionBy: SectionBy? + private lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext + private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From, sectionBy: SectionBy?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate) { let fetchRequest = Internals.CoreStoreFetchRequest() @@ -197,76 +294,7 @@ public final class LiveList { 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!internal self.fetchedResultsController.performFetchFromSpecifiedStores() -// self.taskGroup.notify(queue: .main) { -// -// createAsynchronously(self) -// } -// } - } - else { - - try! self.fetchedResultsController.performFetchFromSpecifiedStores() - } + try! self.fetchedResultsController.performFetchFromSpecifiedStores() } } @@ -301,6 +329,29 @@ extension LiveList: LiveResult { return self.rawObjectWillChange! as! ObservableObjectPublisher } + + public func willChange() { + + #if canImport(Combine) + + #if canImport(SwiftUI) + withAnimation { + + self.objectWillChange.send() + } + + #else + self.objectWillChange.send() + + #endif + + #endif + } + + public func didChange() { + + // TODO: + } } #endif diff --git a/Sources/LiveObject.swift b/Sources/LiveObject.swift new file mode 100644 index 0000000..f11be0d --- /dev/null +++ b/Sources/LiveObject.swift @@ -0,0 +1,285 @@ +// +// LiveObject.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 CoreData + +#if canImport(Combine) +import Combine + +#endif + +#if canImport(SwiftUI) +import SwiftUI + +#endif + + +// MARK: - LiveObject + +@dynamicMemberLookup +public final class LiveObject: Identifiable, Hashable { + + // MARK: Public + + public typealias SectionID = String + public typealias ItemID = O.ObjectID + + public var snapshot: SnapshotType { + + return self.lazySnapshot + } + + public private(set) lazy var object: O = self.context.fetchExisting(self.id)! + + + // MARK: Identifiable + + public let id: O.ObjectID + + + // MARK: Equatable + + public static func == (_ lhs: LiveObject, _ rhs: LiveObject) -> Bool { + + return lhs.id == rhs.id + && lhs.context == rhs.context + } + + + // MARK: Hashable + + public func hash(into hasher: inout Hasher) { + + hasher.combine(self.id) + hasher.combine(self.context) + } + + + // MARK: LiveResult + + public typealias ObjectType = O + + public typealias SnapshotType = ObjectSnapshot + + + // MARK: Internal + + internal convenience init(id: ID, context: NSManagedObjectContext) { + + self.init(id: id, context: context, initializer: ObjectSnapshot.init(id:context:)) + } + + + // MARK: FilePrivate + + fileprivate let rawObjectWillChange: Any? + + fileprivate init(id: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot) { + + self.id = id + self.context = context + if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) { + + #if canImport(Combine) + self.rawObjectWillChange = ObservableObjectPublisher() + + #else + self.rawObjectWillChange = nil + + #endif + } + else { + + self.rawObjectWillChange = nil + } + self.observer = NotificationCenter.default.addObserver( + forName: .NSManagedObjectContextObjectsDidChange, + object: context, + queue: .main, + using: { [weak self] (notification) in + + guard let self = self, let userInfo = notification.userInfo else { + + return + } + let updatedObjects = (userInfo[NSUpdatedObjectsKey] as! NSSet? ?? []) + let mergedObjects = (userInfo[NSRefreshedObjectsKey] as! NSSet? ?? []) + guard mergedObjects.contains(where: { ($0 as! NSManagedObject).objectID == id }) + || updatedObjects.contains(where: { ($0 as! NSManagedObject).objectID == id }) else { + + return + } + self.$lazySnapshot.reset({ initializer(id, context) }) + if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) { + + self.willChange() + } + } + ) + + self.$lazySnapshot.initialize({ initializer(id, context) }) + } + + + // MARK: Private + + private let context: NSManagedObjectContext + private var observer: NSObjectProtocol? + + @Internals.LazyNonmutating(uninitialized: ()) + private var lazySnapshot: ObjectSnapshot +} + + +#if canImport(Combine) +import Combine + +// MARK: - LiveObject: LiveResult + +@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 15.0, *) +extension LiveObject: LiveResult { + + // MARK: ObservableObject + + public var objectWillChange: ObservableObjectPublisher { + + return self.rawObjectWillChange! as! ObservableObjectPublisher + } + + public func willChange() { + + #if canImport(Combine) + + #if canImport(SwiftUI) + withAnimation { + + self.objectWillChange.send() + } + + #else + self.objectWillChange.send() + + #endif + + #endif + } + + public func didChange() { + + // TODO: + } +} + +#endif + + +// MARK: - LiveObject where O: NSManagedObject + +@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)") +extension LiveObject where O: NSManagedObject { + + // MARK: Public + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath) -> V { + + fatalError() +// return self.snapshot[dynamicMember: member] + } +} + + +// MARK: - LiveObject where O: CoreStoreObject + +extension LiveObject where O: CoreStoreObject { + + // MARK: Public + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Required>) -> V { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Optional>) -> V? { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Required>) -> V { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Optional>) -> V? { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.ToOne>) -> D? { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.ToManyOrdered>) -> [D] { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.ToManyUnordered>) -> Set { + + return self.snapshot[dynamicMember: member] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath) -> T { + + return self.object[keyPath: member] + } +} diff --git a/Sources/LiveResult.swift b/Sources/LiveResult.swift index 407eb25..e246116 100644 --- a/Sources/LiveResult.swift +++ b/Sources/LiveResult.swift @@ -41,6 +41,9 @@ public protocol LiveResult: ObservableObject { associatedtype ObjectType associatedtype SnapshotType: SnapshotResult where SnapshotType.ObjectType == Self.ObjectType + + func willChange() + func didChange() } #endif diff --git a/Sources/NSManagedObject+Logging.swift b/Sources/NSManagedObject+Logging.swift index bad70a0..f38ee40 100644 --- a/Sources/NSManagedObject+Logging.swift +++ b/Sources/NSManagedObject+Logging.swift @@ -38,15 +38,7 @@ extension NSManagedObject { return nil } - if context.isTransactionContext { - - return context.parentTransaction?.isRunningInAllowedQueue() - } - if context.isDataStackContext { - - return Thread.isMainThread - } - return nil + return context.isRunningInAllowedQueue() } @nonobjc @@ -56,14 +48,6 @@ extension NSManagedObject { return nil } - if context.isTransactionContext { - - return true - } - if context.isDataStackContext { - - return false - } - return nil + return context.isEditableInContext() } } diff --git a/Sources/NSManagedObjectContext+CoreStore.swift b/Sources/NSManagedObjectContext+CoreStore.swift index 8efda4c..ffe0b9f 100644 --- a/Sources/NSManagedObjectContext+CoreStore.swift +++ b/Sources/NSManagedObjectContext+CoreStore.swift @@ -85,6 +85,35 @@ extension NSManagedObjectContext { } ) } + + @nonobjc + internal func liveObject(id: NSManagedObjectID) -> LiveObject { + + let cache = self.liveObjectsCache(D.self) + return Internals.with { + + if let liveObject = cache.object(forKey: id) { + + return liveObject + } + let liveObject = LiveObject(id: id, context: self) + cache.setObject(liveObject, forKey: id) + return liveObject + } + } + + @nonobjc + private func liveObjectsCache(_ objectType: D.Type) -> NSMapTable> { + + let key = Internals.typeName(objectType) + if let cache = self.userInfo[key] { + + return cache as! NSMapTable> + } + let cache = NSMapTable>.strongToWeakObjects() + self.userInfo[key] = cache + return cache + } // MARK: Private diff --git a/Sources/NSManagedObjectContext+Logging.swift b/Sources/NSManagedObjectContext+Logging.swift new file mode 100644 index 0000000..965508d --- /dev/null +++ b/Sources/NSManagedObjectContext+Logging.swift @@ -0,0 +1,61 @@ +// +// NSManagedObjectContext+Logging.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: - NSManagedObjectContext + +extension NSManagedObjectContext { + + @nonobjc + internal func isRunningInAllowedQueue() -> Bool? { + + if self.isTransactionContext { + + return self.parentTransaction?.isRunningInAllowedQueue() + } + if self.isDataStackContext { + + return Thread.isMainThread + } + return nil + } + + @nonobjc + internal func isEditableInContext() -> Bool? { + + if self.isTransactionContext { + + return true + } + if self.isDataStackContext { + + return false + } + return nil + } +} diff --git a/Sources/ObjectSnapshot.swift b/Sources/ObjectSnapshot.swift index 4f60e45..11d7a66 100644 --- a/Sources/ObjectSnapshot.swift +++ b/Sources/ObjectSnapshot.swift @@ -24,58 +24,78 @@ // import CoreData -import Foundation + +#if canImport(UIKit) +import UIKit + +#elseif canImport(AppKit) +import AppKit + +#endif // MARK: - ObjectSnapshot -/** - An `ObjectSnapshot` contains "snapshot" values from a `DynamicObject` instance copied at a specific point in time. - */ @dynamicMemberLookup -public struct ObjectSnapshot { - - // MARK: FilePrivate - - fileprivate var attributes: [KeyPathString: Any] - - // MARK: Private - - private init() { - - self.attributes = [:] +public struct ObjectSnapshot: SnapshotResult, Identifiable, Hashable { + + // MARK: SnapshotResult + + public typealias ObjectType = O + + + // MARK: Identifiable + + public let id: O.ObjectID + + + // MARK: Equatable + + public static func == (_ lhs: Self, _ rhs: Self) -> Bool { + + return lhs.id == rhs.id + && lhs.values == rhs.values } + + + // MARK: Hashable + + public func hash(into hasher: inout Hasher) { + + hasher.combine(self.id) + hasher.combine(self.values) + } + + + // MARK: Internal + + internal init(id: ID, context: NSManagedObjectContext) { + + self.id = id + self.context = context + self.values = O.cs_snapshotDictionary(id: id, context: context) as NSDictionary + } + + + // MARK: Private + + private let context: NSManagedObjectContext + private let values: NSDictionary } + // MARK: - ObjectSnapshot where O: NSManagedObject +@available(*, unavailable, message: "KeyPaths accessed from @dynamicMemberLookup types can't generate KVC keys yet (https://bugs.swift.org/browse/SR-11351)") extension ObjectSnapshot where O: NSManagedObject { - - /** - Initializes an `ObjectSnapshot` instance by copying all attribute values from the given `NSManagedObject`. - */ - public init(from object: O) { - - self.attributes = object.dictionaryWithValues( - forKeys: Array(object.entity.attributesByName.keys) - ) - } - + /** Returns the value for the property identified by a given key. */ public subscript(dynamicMember member: KeyPath) -> V { - - get { - - let key = String(keyPath: member) - return self.attributes[key]! as! V - } - set { - - let key = String(keyPath: member) - self.attributes[key] = newValue - } + + let key = String(keyPath: member) + return self.values[key] as! V } } @@ -83,61 +103,67 @@ extension ObjectSnapshot where O: NSManagedObject { // MARK: - ObjectSnapshot where O: CoreStoreObject extension ObjectSnapshot where O: CoreStoreObject { - - /** - Initializes an `ObjectSnapshot` instance by copying all attribute values from the given `CoreStoreObject`. - */ - public init(from object: O) { - - var attributes: [KeyPathString: Any] = [:] - Self.initializeAttributes( - mirror: Mirror(reflecting: object), - object: object, - into: &attributes - ) - self.attributes = attributes - } - + /** Returns the value for the property identified by a given key. */ - public subscript(dynamicMember member: KeyPath) -> K.ReturnValueType { - - get { - - let key = String(keyPath: member) - return self.attributes[key]! as! K.ReturnValueType - } - set { - - let key = String(keyPath: member) - self.attributes[key] = newValue - } + public subscript(dynamicMember member: KeyPath.Required>) -> V { + + let key = String(keyPath: member) + return self.values[key] as! V } - - - // MARK: Private - - private static func initializeAttributes(mirror: Mirror, object: CoreStoreObject, into attributes: inout [KeyPathString: Any]) { - - if let superClassMirror = mirror.superclassMirror { - - self.initializeAttributes( - mirror: superClassMirror, - object: object, - into: &attributes - ) - } - for child in mirror.children { - - switch child.value { - - case let property as AttributeProtocol: - attributes[property.keyPath] = property.valueForSnapshot - - default: - continue - } - } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Optional>) -> V? { + + let key = String(keyPath: member) + return self.values[key] as! V? + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Required>) -> V { + + let key = String(keyPath: member) + return self.values[key] as! V + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.Optional>) -> V? { + + let key = String(keyPath: member) + return self.values[key] as! V? + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.ToOne>) -> D? { + + let key = String(keyPath: member) + return self.values[key] as! D? + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.ToManyOrdered>) -> [D] { + + let key = String(keyPath: member) + return self.values[key] as! [D] + } + + /** + Returns the value for the property identified by a given key. + */ + public subscript(dynamicMember member: KeyPath.ToManyUnordered>) -> Set { + + let key = String(keyPath: member) + return self.values[key] as! Set } } diff --git a/Sources/Relationship.swift b/Sources/Relationship.swift index ae130a8..6fe5419 100644 --- a/Sources/Relationship.swift +++ b/Sources/Relationship.swift @@ -308,6 +308,11 @@ public enum RelationshipContainer { } } } + + internal var valueForSnapshot: Any { + + return self.value as Any + } // MARK: Private @@ -595,6 +600,11 @@ public enum RelationshipContainer { } } } + + internal var valueForSnapshot: Any { + + return self.value as Any + } // MARK: Private @@ -887,6 +897,11 @@ public enum RelationshipContainer { } } } + + internal var valueForSnapshot: Any { + + return self.value as Any + } // MARK: Private diff --git a/Sources/RelationshipProtocol.swift b/Sources/RelationshipProtocol.swift index bb97636..31e4293 100644 --- a/Sources/RelationshipProtocol.swift +++ b/Sources/RelationshipProtocol.swift @@ -42,4 +42,5 @@ internal protocol RelationshipProtocol: AnyObject { var renamingIdentifier: () -> String? { get } var minCount: Int { get } var maxCount: Int { get } + var valueForSnapshot: Any { get } } diff --git a/Sources/Transformable.swift b/Sources/Transformable.swift index dda5bab..52ac645 100644 --- a/Sources/Transformable.swift +++ b/Sources/Transformable.swift @@ -269,7 +269,8 @@ public enum TransformableContainer { } internal var valueForSnapshot: Any { - return self.value + + return self.value as Any } @@ -486,6 +487,7 @@ public enum TransformableContainer { } internal var valueForSnapshot: Any { + return self.value as Any } diff --git a/Sources/Value.swift b/Sources/Value.swift index 73be9f6..f4c2d28 100644 --- a/Sources/Value.swift +++ b/Sources/Value.swift @@ -264,7 +264,8 @@ public enum ValueContainer { } internal var valueForSnapshot: Any { - return self.value + + return self.value as Any } @@ -481,6 +482,7 @@ public enum ValueContainer { } internal var valueForSnapshot: Any { + return self.value as Any }