mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-14 13:13:33 +01:00
Merge branch 'master' into prototype/containers
This commit is contained in:
BIN
CoreStore.png
BIN
CoreStore.png
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 61 KiB |
BIN
CoreStore.sketch
BIN
CoreStore.sketch
Binary file not shown.
@@ -693,6 +693,10 @@
|
||||
B5B866E125E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866DF25E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift */; };
|
||||
B5B866E225E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866DF25E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift */; };
|
||||
B5B866E325E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866DF25E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift */; };
|
||||
B5B866ED25F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */; };
|
||||
B5B866EE25F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */; };
|
||||
B5B866EF25F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */; };
|
||||
B5B866F025F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */; };
|
||||
B5BF7FAD234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */; };
|
||||
B5BF7FAE234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */; };
|
||||
B5BF7FAF234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */; };
|
||||
@@ -721,14 +725,14 @@
|
||||
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 */; };
|
||||
B5C7958F25D7D18000BDACC1 /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* LiveList.swift */; };
|
||||
B5C7959025D7D18000BDACC1 /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* LiveList.swift */; };
|
||||
B5C7959125D7D18000BDACC1 /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* LiveList.swift */; };
|
||||
B5C7959225D7D18000BDACC1 /* LiveList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* LiveList.swift */; };
|
||||
B5C7959425D7D18700BDACC1 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* LiveObject.swift */; };
|
||||
B5C7959525D7D18700BDACC1 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* LiveObject.swift */; };
|
||||
B5C7959625D7D18700BDACC1 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* LiveObject.swift */; };
|
||||
B5C7959725D7D18700BDACC1 /* LiveObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* LiveObject.swift */; };
|
||||
B5C7958F25D7D18000BDACC1 /* ListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* ListState.swift */; };
|
||||
B5C7959025D7D18000BDACC1 /* ListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* ListState.swift */; };
|
||||
B5C7959125D7D18000BDACC1 /* ListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* ListState.swift */; };
|
||||
B5C7959225D7D18000BDACC1 /* ListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7958E25D7D18000BDACC1 /* ListState.swift */; };
|
||||
B5C7959425D7D18700BDACC1 /* ObjectState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* ObjectState.swift */; };
|
||||
B5C7959525D7D18700BDACC1 /* ObjectState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* ObjectState.swift */; };
|
||||
B5C7959625D7D18700BDACC1 /* ObjectState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* ObjectState.swift */; };
|
||||
B5C7959725D7D18700BDACC1 /* ObjectState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959325D7D18700BDACC1 /* ObjectState.swift */; };
|
||||
B5C7959925D7D8B300BDACC1 /* ListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959825D7D8B300BDACC1 /* ListReader.swift */; };
|
||||
B5C7959A25D7D8B300BDACC1 /* ListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959825D7D8B300BDACC1 /* ListReader.swift */; };
|
||||
B5C7959B25D7D8B300BDACC1 /* ListReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7959825D7D8B300BDACC1 /* ListReader.swift */; };
|
||||
@@ -1157,6 +1161,7 @@
|
||||
B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreBridge.swift; sourceTree = "<group>"; };
|
||||
B5B866DA25E9012F00335476 /* ListPublisher+Reactive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListPublisher+Reactive.swift"; sourceTree = "<group>"; };
|
||||
B5B866DF25E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisher.SnapshotPublisher.swift; sourceTree = "<group>"; };
|
||||
B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStack.AddStoragePublisher.swift; sourceTree = "<group>"; };
|
||||
B5BF7FAC234C41E90070E741 /* Internals.DiffableDataSourceSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.DiffableDataSourceSnapshot.swift; sourceTree = "<group>"; };
|
||||
B5BF7FB1234C97910070E741 /* DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiffableDataSource.swift; sourceTree = "<group>"; };
|
||||
B5BF7FB6234C97CE0070E741 /* DiffableDataSource.TableViewAdapter-UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiffableDataSource.TableViewAdapter-UIKit.swift"; sourceTree = "<group>"; };
|
||||
@@ -1164,8 +1169,8 @@
|
||||
B5BF7FC0234D7B2E0070E741 /* ObjectPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectPublisher.swift; sourceTree = "<group>"; };
|
||||
B5BF7FC5234D7E460070E741 /* ObjectSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectSnapshot.swift; sourceTree = "<group>"; };
|
||||
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.LazyNonmutating.swift; sourceTree = "<group>"; };
|
||||
B5C7958E25D7D18000BDACC1 /* LiveList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveList.swift; sourceTree = "<group>"; };
|
||||
B5C7959325D7D18700BDACC1 /* LiveObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveObject.swift; sourceTree = "<group>"; };
|
||||
B5C7958E25D7D18000BDACC1 /* ListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListState.swift; sourceTree = "<group>"; };
|
||||
B5C7959325D7D18700BDACC1 /* ObjectState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectState.swift; sourceTree = "<group>"; };
|
||||
B5C7959825D7D8B300BDACC1 /* ListReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListReader.swift; sourceTree = "<group>"; };
|
||||
B5C795A025D7EB2200BDACC1 /* ForEach+SwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ForEach+SwiftUI.swift"; sourceTree = "<group>"; };
|
||||
B5C795C225DD651F00BDACC1 /* DataStack+Reactive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataStack+Reactive.swift"; sourceTree = "<group>"; };
|
||||
@@ -1740,8 +1745,8 @@
|
||||
B5C7959D25D7E89B00BDACC1 /* PropertyWrappers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5C7958E25D7D18000BDACC1 /* LiveList.swift */,
|
||||
B5C7959325D7D18700BDACC1 /* LiveObject.swift */,
|
||||
B5C7958E25D7D18000BDACC1 /* ListState.swift */,
|
||||
B5C7959325D7D18700BDACC1 /* ObjectState.swift */,
|
||||
);
|
||||
name = PropertyWrappers;
|
||||
sourceTree = "<group>";
|
||||
@@ -1769,6 +1774,7 @@
|
||||
B5C795C225DD651F00BDACC1 /* DataStack+Reactive.swift */,
|
||||
B5B866DA25E9012F00335476 /* ListPublisher+Reactive.swift */,
|
||||
B5944EF525E269F9001D1D81 /* ObjectPublisher+Reactive.swift */,
|
||||
B5B866EC25F4800800335476 /* DataStack.AddStoragePublisher.swift */,
|
||||
B5944EFA25E8E8DA001D1D81 /* ListPublisher.SnapshotPublisher.swift */,
|
||||
B5B866DF25E9048000335476 /* ObjectPublisher.SnapshotPublisher.swift */,
|
||||
);
|
||||
@@ -2356,6 +2362,7 @@
|
||||
B5AA37F1235C28EE00FFD4B9 /* DiffableDataSource.CollectionViewAdapter-AppKit.swift in Sources */,
|
||||
B5D7A5B61CA3BF8F005C752B /* CSInto.swift in Sources */,
|
||||
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
|
||||
B5B866ED25F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */,
|
||||
B5DE522B230BD7CC00A22534 /* Internals.swift in Sources */,
|
||||
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
B509D7BC23C847BC00F42824 /* Value.Optional.swift in Sources */,
|
||||
@@ -2414,7 +2421,7 @@
|
||||
B5E84F2F1AFF849C0064E85B /* Internals.NotificationObserver.swift in Sources */,
|
||||
B5F1DA8D1B9AA97D007C5CBB /* ImportableObject.swift in Sources */,
|
||||
B56965241B356B820075EE4A /* MigrationResult.swift in Sources */,
|
||||
B5C7958F25D7D18000BDACC1 /* LiveList.swift in Sources */,
|
||||
B5C7958F25D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||
B5FE4DAC1C85D44E00FA6A91 /* SQLiteStore.swift in Sources */,
|
||||
B501FDE71CA8D20500BE22EF /* CSListObserver.swift in Sources */,
|
||||
B5E41EC01EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
||||
@@ -2487,7 +2494,7 @@
|
||||
B5C795D225E0DD1B00BDACC1 /* ListSnapshot.SectionInfo.swift in Sources */,
|
||||
B51B5C2B22D43931009FA3BA /* String+KeyPaths.swift in Sources */,
|
||||
B512607F1E97A18000402229 /* CoreStoreObject+Convenience.swift in Sources */,
|
||||
B5C7959425D7D18700BDACC1 /* LiveObject.swift in Sources */,
|
||||
B5C7959425D7D18700BDACC1 /* ObjectState.swift in Sources */,
|
||||
B509D7CE23C8492800F42824 /* Relationship.ToManyUnordered.swift in Sources */,
|
||||
B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */,
|
||||
B5D4A6B723A236DC00D7373F /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||
@@ -2608,6 +2615,7 @@
|
||||
82BA18C21C4BBD5300A0916E /* ObjectMonitor.swift in Sources */,
|
||||
B5D7A5B81CA3BF8F005C752B /* CSInto.swift in Sources */,
|
||||
82BA18BD1C4BBD4A00A0916E /* GroupBy.swift in Sources */,
|
||||
B5B866EE25F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */,
|
||||
B5ECDC1F1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
|
||||
B5BF7FCC234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */,
|
||||
B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
|
||||
@@ -2665,7 +2673,7 @@
|
||||
B501FDE41CA8D1F500BE22EF /* CSListMonitor.swift in Sources */,
|
||||
B5A1DAC91F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||
B5FE4DA31C8481E100FA6A91 /* StorageInterface.swift in Sources */,
|
||||
B5C7959025D7D18000BDACC1 /* LiveList.swift in Sources */,
|
||||
B5C7959025D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||
B5ECDC131CA816E500C7F112 /* CSTweak.swift in Sources */,
|
||||
B56923C51EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
|
||||
82BA18C91C4BBD5900A0916E /* MigrationType.swift in Sources */,
|
||||
@@ -2735,7 +2743,7 @@
|
||||
B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
|
||||
B5277673234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B509D7D923C84E2600F42824 /* Transformable.Optional.swift in Sources */,
|
||||
B5C7959525D7D18700BDACC1 /* LiveObject.swift in Sources */,
|
||||
B5C7959525D7D18700BDACC1 /* ObjectState.swift in Sources */,
|
||||
B5C795D325E0DD1B00BDACC1 /* ListSnapshot.SectionInfo.swift in Sources */,
|
||||
B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
82BA18A81C4BBD2900A0916E /* CoreStoreLogger.swift in Sources */,
|
||||
@@ -2860,6 +2868,7 @@
|
||||
B50E175523517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||
B5A5F26A1CAEC50F004AB9AF /* CSSelect.swift in Sources */,
|
||||
B5FEC1911C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */,
|
||||
B5B866F025F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */,
|
||||
B52DD1AB1BE1F93900949AFE /* From.swift in Sources */,
|
||||
B52DD1A11BE1F92C00949AFE /* DataStack+Transaction.swift in Sources */,
|
||||
B5220E1C1D130801009BC71E /* Internals.FetchedResultsControllerDelegate.swift in Sources */,
|
||||
@@ -2917,7 +2926,7 @@
|
||||
B52DD19D1BE1F92C00949AFE /* BaseDataTransaction.swift in Sources */,
|
||||
B5220E131D1305ED009BC71E /* SectionBy.swift in Sources */,
|
||||
B559CD4D1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */,
|
||||
B5C7959225D7D18000BDACC1 /* LiveList.swift in Sources */,
|
||||
B5C7959225D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||
B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
||||
B5ECDBE91CA6BEA300C7F112 /* CSClauseTypes.swift in Sources */,
|
||||
B5A1DACB1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||
@@ -2990,7 +2999,7 @@
|
||||
B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */,
|
||||
B5C795D525E0DD1B00BDACC1 /* ListSnapshot.SectionInfo.swift in Sources */,
|
||||
B509D7DB23C84E2600F42824 /* Transformable.Optional.swift in Sources */,
|
||||
B5C7959725D7D18700BDACC1 /* LiveObject.swift in Sources */,
|
||||
B5C7959725D7D18700BDACC1 /* ObjectState.swift in Sources */,
|
||||
B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
B5277675234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
@@ -3112,6 +3121,7 @@
|
||||
B56321891BD65216006C9394 /* AsynchronousDataTransaction.swift in Sources */,
|
||||
B5ECDC201CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
|
||||
B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
|
||||
B5B866EF25F4800800335476 /* DataStack.AddStoragePublisher.swift in Sources */,
|
||||
B5BF7FCD234D80910070E741 /* Internals.LazyNonmutating.swift in Sources */,
|
||||
B53FBA151CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
|
||||
B50564D52350CC3100482308 /* PropertyProtocol.swift in Sources */,
|
||||
@@ -3168,7 +3178,7 @@
|
||||
B501FDE51CA8D1F500BE22EF /* CSListMonitor.swift in Sources */,
|
||||
B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
||||
B5ECDC141CA816E500C7F112 /* CSTweak.swift in Sources */,
|
||||
B5C7959125D7D18000BDACC1 /* LiveList.swift in Sources */,
|
||||
B5C7959125D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||
B5A1DACA1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||
B56321AE1BD6521C006C9394 /* Internals.NotificationObserver.swift in Sources */,
|
||||
B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */,
|
||||
@@ -3241,7 +3251,7 @@
|
||||
B5277674234F1AEB0056BE9F /* NSManagedObjectContext+Logging.swift in Sources */,
|
||||
B509D7DA23C84E2600F42824 /* Transformable.Optional.swift in Sources */,
|
||||
B5C795D425E0DD1B00BDACC1 /* ListSnapshot.SectionInfo.swift in Sources */,
|
||||
B5C7959625D7D18700BDACC1 /* LiveObject.swift in Sources */,
|
||||
B5C7959625D7D18700BDACC1 /* ObjectState.swift in Sources */,
|
||||
B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */,
|
||||
B563218B1BD65216006C9394 /* UnsafeDataTransaction.swift in Sources */,
|
||||
B549F6601E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
|
||||
@@ -3372,7 +3382,7 @@
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 11.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
||||
@@ -3436,7 +3446,7 @@
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 11.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
@@ -3455,7 +3465,7 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3478,7 +3488,7 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3538,7 +3548,7 @@
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3565,7 +3575,7 @@
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3631,7 +3641,7 @@
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3660,7 +3670,7 @@
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3726,7 +3736,7 @@
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
@@ -3755,7 +3765,7 @@
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MARKETING_VERSION = 7.3.1;
|
||||
MARKETING_VERSION = 8.0.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"-weak_framework",
|
||||
Combine,
|
||||
|
||||
@@ -191,16 +191,8 @@
|
||||
XCTAssertEqualObjects([[sqliteStorage class] storeType], [CSSQLiteStore storeType]);
|
||||
XCTAssertEqualObjects([[sqliteStorage class] storeType], NSSQLiteStoreType);
|
||||
XCTAssertNil(sqliteStorage.configuration);
|
||||
NSDictionary *storeOptions;
|
||||
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *)) {
|
||||
|
||||
storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" },
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: @YES };
|
||||
}
|
||||
else {
|
||||
|
||||
storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" }};
|
||||
}
|
||||
NSDictionary *storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" },
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: @YES };
|
||||
XCTAssertEqualObjects(sqliteStorage.storeOptions, storeOptions);
|
||||
XCTAssertNil(sqliteError);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import CoreStore
|
||||
|
||||
// MARK: - ConvenienceTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class ConvenienceTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
|
||||
@@ -31,7 +31,6 @@ import CoreStore
|
||||
|
||||
// MARK: - ListObserverTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
@@ -551,7 +550,6 @@ class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
// MARK: TestListObserver
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class TestListObserver: ListSectionObserver {
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
@@ -31,7 +31,6 @@ import CoreStore
|
||||
|
||||
// MARK: - ObjectObserverTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
@@ -203,7 +202,6 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
// MARK: TestObjectObserver
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class TestObjectObserver: ObjectObserver {
|
||||
|
||||
typealias ObjectEntityType = TestEntity1
|
||||
|
||||
@@ -31,7 +31,6 @@ import CoreStore
|
||||
|
||||
// MARK: - ObjectPublisherTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
class ObjectPublisherTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
|
||||
@@ -31,7 +31,6 @@ import CoreStore
|
||||
|
||||
//MARK: - SectionByTests
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
final class SectionByTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
|
||||
@@ -85,21 +85,11 @@ final class StorageInterfaceTests: XCTestCase {
|
||||
let store = SQLiteStore()
|
||||
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
|
||||
XCTAssertNil(store.configuration)
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
|
||||
)
|
||||
}
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
|
||||
XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL)
|
||||
XCTAssertTrue(store.migrationMappingProviders.isEmpty)
|
||||
@@ -125,21 +115,11 @@ final class StorageInterfaceTests: XCTestCase {
|
||||
)
|
||||
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
|
||||
)
|
||||
}
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
|
||||
XCTAssertEqual(store.fileURL, fileURL)
|
||||
XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider])
|
||||
@@ -162,21 +142,11 @@ final class StorageInterfaceTests: XCTestCase {
|
||||
)
|
||||
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
|
||||
)
|
||||
}
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
|
||||
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.defaultRootDirectory)
|
||||
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
|
||||
@@ -213,21 +183,11 @@ final class StorageInterfaceTests: XCTestCase {
|
||||
let store = SQLiteStore.legacy()
|
||||
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
|
||||
XCTAssertNil(store.configuration)
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
|
||||
)
|
||||
}
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
|
||||
XCTAssertEqual(store.fileURL, SQLiteStore.legacyDefaultFileURL)
|
||||
XCTAssertTrue(store.migrationMappingProviders.isEmpty)
|
||||
@@ -250,21 +210,11 @@ final class StorageInterfaceTests: XCTestCase {
|
||||
)
|
||||
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
|
||||
)
|
||||
}
|
||||
XCTAssertEqual(
|
||||
store.storeOptions as NSDictionary?,
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
|
||||
)
|
||||
|
||||
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.legacyDefaultRootDirectory)
|
||||
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
|
||||
|
||||
@@ -383,8 +383,6 @@ final class TransactionTests: BaseTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
dynamic func test_ThatSynchronousTransactions_CanCommitWithoutWaitingForMerges() {
|
||||
|
||||
|
||||
Binary file not shown.
@@ -71,7 +71,7 @@ extension Modern.ColorsDemo {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@LiveList(Modern.ColorsDemo.palettesPublisher)
|
||||
@ListState(Modern.ColorsDemo.palettesPublisher)
|
||||
private var palettes: ListSnapshot
|
||||
|
||||
private let listView: (
|
||||
|
||||
@@ -15,9 +15,9 @@ extension Modern.ColorsDemo.SwiftUI {
|
||||
struct DetailView: View {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Using a `LiveObject` to observe object changes. Note that the `ObjectSnapshot` is always `Optional`
|
||||
⭐️ Sample 1: Using a `ObjectState` to observe object changes. Note that the `ObjectSnapshot` is always `Optional`
|
||||
*/
|
||||
@LiveObject
|
||||
@ObjectState
|
||||
private var palette: ObjectSnapshot<Modern.ColorsDemo.Palette>?
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,13 +14,13 @@ extension Modern.ColorsDemo.SwiftUI {
|
||||
struct ItemView: View {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Using a `LiveObject` to observe object changes. Note that the `ObjectSnapshot` is always `Optional`
|
||||
⭐️ Sample 1: Using a `ObjectState` to observe object changes. Note that the `ObjectSnapshot` is always `Optional`
|
||||
*/
|
||||
@LiveObject
|
||||
@ObjectState
|
||||
private var palette: ObjectSnapshot<Modern.ColorsDemo.Palette>?
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Initializing a `LiveObject` from an existing `ObjectPublisher`
|
||||
⭐️ Sample 2: Initializing a `ObjectState` from an existing `ObjectPublisher`
|
||||
*/
|
||||
internal init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ extension Modern.ColorsDemo.SwiftUI {
|
||||
struct ListView: View {
|
||||
|
||||
/**
|
||||
⭐️ Sample 1: Using a `LiveList` to observe list changes
|
||||
⭐️ Sample 1: Using a `ListState` to observe list changes
|
||||
*/
|
||||
@LiveList
|
||||
@ListState
|
||||
private var palettes: ListSnapshot<Modern.ColorsDemo.Palette>
|
||||
|
||||
/**
|
||||
⭐️ Sample 2: Initializing a `LiveList` from an existing `ListPublisher`
|
||||
⭐️ Sample 2: Initializing a `ListState` from an existing `ListPublisher`
|
||||
*/
|
||||
init(
|
||||
listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
|
||||
|
||||
@@ -25,7 +25,7 @@ extension Modern.PlacemarksDemo {
|
||||
Modern.PlacemarksDemo.dataStack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let place = self.place.asEditable(in: transaction)
|
||||
let place = self.$place?.asEditable(in: transaction)
|
||||
place?.annotation = .init(coordinate: coordinate)
|
||||
},
|
||||
completion: { _ in }
|
||||
@@ -42,7 +42,7 @@ extension Modern.PlacemarksDemo {
|
||||
_ = try? Modern.PlacemarksDemo.dataStack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
let place = self.place.asEditable(in: transaction)
|
||||
let place = self.$place?.asEditable(in: transaction)
|
||||
place?.setRandomLocation()
|
||||
}
|
||||
)
|
||||
@@ -71,22 +71,25 @@ extension Modern.PlacemarksDemo {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@ObservedObject
|
||||
var place: ObjectPublisher<Modern.PlacemarksDemo.Place>
|
||||
@ObjectState(Modern.PlacemarksDemo.placePublisher)
|
||||
var place: ObjectSnapshot<Modern.PlacemarksDemo.Place>?
|
||||
|
||||
init() {
|
||||
|
||||
self.place = Modern.PlacemarksDemo.placePublisher
|
||||
self.sinkCancellable = self.place.sink(
|
||||
self.sinkCancellable = self.$place?.reactive.snapshot().sink(
|
||||
receiveCompletion: { _ in
|
||||
|
||||
// Deleted, do nothing
|
||||
},
|
||||
receiveValue: { [self] (snapshot) in
|
||||
|
||||
guard let snapshot = snapshot else {
|
||||
|
||||
return
|
||||
}
|
||||
self.geocoder.geocode(place: snapshot) { (title, subtitle) in
|
||||
|
||||
guard self.place.snapshot == snapshot else {
|
||||
guard self.place == snapshot else {
|
||||
|
||||
return
|
||||
}
|
||||
@@ -104,22 +107,29 @@ extension Modern.PlacemarksDemo {
|
||||
// MARK: View
|
||||
|
||||
var body: some View {
|
||||
Modern.PlacemarksDemo.MapView(
|
||||
place: self.place.snapshot,
|
||||
onTap: { coordinate in
|
||||
|
||||
Group {
|
||||
|
||||
if let place = self.place {
|
||||
|
||||
self.demoAsynchronousTransaction(coordinate: coordinate)
|
||||
Modern.PlacemarksDemo.MapView(
|
||||
place: place,
|
||||
onTap: { coordinate in
|
||||
|
||||
self.demoAsynchronousTransaction(coordinate: coordinate)
|
||||
}
|
||||
)
|
||||
.overlay(
|
||||
InstructionsView(
|
||||
("Random", "Sets random coordinate"),
|
||||
("Tap", "Sets to tapped coordinate")
|
||||
)
|
||||
.padding(.leading, 10)
|
||||
.padding(.bottom, 40),
|
||||
alignment: .bottomLeading
|
||||
)
|
||||
}
|
||||
)
|
||||
.overlay(
|
||||
InstructionsView(
|
||||
("Random", "Sets random coordinate"),
|
||||
("Tap", "Sets to tapped coordinate")
|
||||
)
|
||||
.padding(.leading, 10)
|
||||
.padding(.bottom, 40),
|
||||
alignment: .bottomLeading
|
||||
)
|
||||
}
|
||||
.navigationBarTitle("Placemarks")
|
||||
.navigationBarItems(
|
||||
trailing: Button("Random") {
|
||||
@@ -132,7 +142,7 @@ extension Modern.PlacemarksDemo {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var sinkCancellable: AnyCancellable? = nil
|
||||
private var sinkCancellable: AnyCancellable?
|
||||
private let geocoder = Modern.PlacemarksDemo.Geocoder()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ extension Modern.PokedexDemo {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Modern.PokedexDemo.PokedexEntry>()
|
||||
.orderBy(.ascending(\.$index)),
|
||||
in: Modern.PokedexDemo.dataStack
|
||||
|
||||
593
README.md
593
README.md
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img alt="CoreStore" src="https://cloud.githubusercontent.com/assets/3029684/13373932/84daee2a-ddb8-11e5-99db-fdf415620102.png" width=614 />
|
||||
<img alt="CoreStore" src="https://github.com/JohnEstropia/CoreStore/raw/develop/CoreStore.png" width=614 />
|
||||
<br />
|
||||
<br />
|
||||
Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
@@ -24,38 +24,12 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
|
||||
Upgrading from previous CoreStore versions? Check out the [🆕 features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
|
||||
|
||||
CoreStore is now part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
|
||||
|
||||
|
||||
## Why use CoreStore?
|
||||
|
||||
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices.
|
||||
|
||||
### Features
|
||||
|
||||
- **SwiftUI and Combine API utilities.**
|
||||
- **Backwards-portable DiffableDataSources implementation!** `UITableViews` and `UICollectionViews` now have a new ally: `ListPublisher`s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to `UITableViews` and `UICollectionViews` reload errors!
|
||||
- **💎Tight design around Swift’s code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
|
||||
- **🚦Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
|
||||
- **🔍Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (`min`, `max`, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
|
||||
- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
|
||||
- **📥Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
|
||||
- **🗑Say goodbye to *.xcdatamodeld* files!** While CoreStore supports `NSManagedObject`s, it offers `CoreStoreObject` whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
|
||||
- **🔗Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
|
||||
- **Easier custom migrations.** Say goodbye to *.xcmappingmodel* files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. *(See [Migrations](#migrations))*
|
||||
- **📝Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
|
||||
- **⛓Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
|
||||
- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names.
|
||||
- **📙Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
|
||||
- **ℹ️Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
|
||||
- **🎗Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
|
||||
- **🛡More extensive Unit Tests.** Extending CoreStore is safe without having to worry about breaking old behavior.
|
||||
|
||||
*Have ideas that may benefit other Core Data users? [Feature Request](https://github.com/JohnEstropia/CoreStore/issues)s are welcome!*
|
||||
CoreStore is part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
|
||||
|
||||
## Contents
|
||||
|
||||
- [TL;DR (a.k.a. sample codes)](#tldr-aka-sample-codes)
|
||||
- [Why use CoreStore?](#why-use-corestore)
|
||||
- [Architecture](#architecture)
|
||||
- CoreStore Tutorials (All of these have demos in the **Demo** app project!)
|
||||
- [Setting up](#setting-up)
|
||||
@@ -95,13 +69,28 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
|
||||
- [Observe detailed list changes](#observe-detailed-list-changes)
|
||||
- [Objective-C support](#objective-c-support)
|
||||
- [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects)
|
||||
- 🆕[New `@Field` Property Wrapper syntax](#new-field-property-wrapper-syntax)
|
||||
- 🆕[`@Field.Stored` ](#fieldstored)
|
||||
- 🆕[`@Field.Virtual` ](#fieldvirtual)
|
||||
- 🆕[`@Field.Coded` ](#fieldcoded)
|
||||
- 🆕[`@Field.Relationship` ](#fieldrelationship)
|
||||
- 🆕[`@Field` usage notes](#field-usage-notes)
|
||||
- [New `@Field` Property Wrapper syntax](#new-field-property-wrapper-syntax)
|
||||
- [`@Field.Stored` ](#fieldstored)
|
||||
- [`@Field.Virtual` ](#fieldvirtual)
|
||||
- [`@Field.Coded` ](#fieldcoded)
|
||||
- [`@Field.Relationship` ](#fieldrelationship)
|
||||
- [`@Field` usage notes](#field-usage-notes)
|
||||
- [`VersionLock`s](#versionlocks)
|
||||
- [Reactive Programming](#reactive-programming)
|
||||
- [RxSwift](#rxswift)
|
||||
- 🆕[Combine](#combine)
|
||||
- 🆕[`DataStack.reactive`](#datastackreactive)
|
||||
- 🆕[`ListPublisher.reactive`](#listpublisherreactive)
|
||||
- 🆕[`ObjectPublisher.reactive`](#objectpublisherreactive)
|
||||
- 🆕[SwiftUI Utilities](#swiftui-utilities)
|
||||
- 🆕[SwiftUI Views`](#swiftui-views)
|
||||
- 🆕[`ListReader`](#listreader)
|
||||
- 🆕[`ObjectReader`](#objectreader)
|
||||
- 🆕[SwiftUI Property Wrappers](#swiftui-property-wrappers)
|
||||
- 🆕[`ListState`](#liststate)
|
||||
- 🆕[`ObjectState`](#objectstate)
|
||||
- 🆕[SwiftUI Extensions](#swiftui-extensions)
|
||||
- 🆕[`ForEach`](#foreach)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Installation](#installation)
|
||||
- [Changesets](#changesets)
|
||||
@@ -113,6 +102,18 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
|
||||
|
||||
## TL;DR (a.k.a. sample codes)
|
||||
|
||||
Pure-Swift models:
|
||||
```swift
|
||||
class Person: CoreStoreObject {
|
||||
@Field.Stored("name")
|
||||
var name: String = ""
|
||||
|
||||
@Field.Relationship("pets", inverse: \Dog.$master)
|
||||
var pets: Set<Dog>
|
||||
}
|
||||
```
|
||||
(Classic `NSManagedObject`s also supported)
|
||||
|
||||
Setting-up with progressive migration support:
|
||||
```swift
|
||||
dataStack = DataStack(
|
||||
@@ -135,7 +136,7 @@ Starting transactions:
|
||||
```swift
|
||||
dataStack.perform(
|
||||
asynchronous: { (transaction) -> Void in
|
||||
let person = transaction.create(Into<MyPersonEntity>())
|
||||
let person = transaction.create(Into<Person>())
|
||||
person.name = "John Smith"
|
||||
person.age = 42
|
||||
},
|
||||
@@ -150,13 +151,13 @@ dataStack.perform(
|
||||
|
||||
Fetching objects (simple):
|
||||
```swift
|
||||
let people = try dataStack.fetchAll(From<MyPersonEntity>())
|
||||
let people = try dataStack.fetchAll(From<Person>())
|
||||
```
|
||||
|
||||
Fetching objects (complex):
|
||||
```swift
|
||||
let people = try dataStack.fetchAll(
|
||||
From<MyPersonEntity>()
|
||||
From<Person>()
|
||||
.where(\.age > 30),
|
||||
.orderBy(.ascending(\.name), .descending(.\age)),
|
||||
.tweak({ $0.includesPendingChanges = false })
|
||||
@@ -166,7 +167,7 @@ let people = try dataStack.fetchAll(
|
||||
Querying values:
|
||||
```swift
|
||||
let maxAge = try dataStack.queryValue(
|
||||
From<MyPersonEntity>()
|
||||
From<Person>()
|
||||
.select(Int.self, .maximum(\.age))
|
||||
)
|
||||
```
|
||||
@@ -176,6 +177,33 @@ But really, there's a reason I wrote this huge *README*. Read up on the details!
|
||||
Check out the **Demo** app project for sample codes as well!
|
||||
|
||||
|
||||
## Why use CoreStore?
|
||||
|
||||
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices.
|
||||
|
||||
### Features
|
||||
|
||||
- **🆕[SwiftUI](#swiftui-utilities) and [Combine](#combine) API utilities.** `ListPublisher`s and `ObjectPublisher`s now have their `@ListState` and `@ObjectState` SwiftUI property wrappers. Combine `Publisher` s are also available through the `ListPublisher.reactive`, `ObjectPublisher.reactive`, and `DataStack.reactive` namespaces.
|
||||
- **Backwards-portable [DiffableDataSources implementation](#observe-a-diffable-list)!** `UITableViews` and `UICollectionViews` now have a new ally: `ListPublisher`s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to `UITableViews` and `UICollectionViews` reload errors!
|
||||
- **💎Tight design around Swift’s code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
|
||||
- **🚦Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
|
||||
- **🔍Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (`min`, `max`, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
|
||||
- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
|
||||
- **📥Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
|
||||
- **🗑Say goodbye to *.xcdatamodeld* files!** While CoreStore supports `NSManagedObject`s, it offers `CoreStoreObject` whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
|
||||
- **🔗Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
|
||||
- **Easier custom migrations.** Say goodbye to *.xcmappingmodel* files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. *(See [Migrations](#migrations))*
|
||||
- **📝Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
|
||||
- **⛓Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
|
||||
- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names.
|
||||
- **📙Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
|
||||
- **ℹ️Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
|
||||
- **🎗Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
|
||||
- **🛡More extensive Unit Tests.** Extending CoreStore is safe without having to worry about breaking old behavior.
|
||||
|
||||
*Have ideas that may benefit other Core Data users? [Feature Request](https://github.com/JohnEstropia/CoreStore/issues)s are welcome!*
|
||||
|
||||
|
||||
|
||||
## Architecture
|
||||
For maximum safety and performance, CoreStore will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of CoreStore before you use it in your apps.
|
||||
@@ -271,6 +299,19 @@ try dataStack.addStorageAndWait(
|
||||
)
|
||||
)
|
||||
```
|
||||
Asynchronous variant:
|
||||
```swift
|
||||
try dataStack.addStorage(
|
||||
InMemoryStore(
|
||||
configuration: "Config2
|
||||
),
|
||||
completion: { storage in
|
||||
// ...
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
(A reactive-programming variant of this method is explained in detail in the section on [`DataStack` Combine publishers](#datastackreactive))
|
||||
|
||||
### Local Store
|
||||
The most common `StorageInterface` you will probably use is the `SQLiteStore`, which saves data in a local SQLite file.
|
||||
@@ -292,7 +333,9 @@ CoreStore can decide the default values for these properties, so `SQLiteStore`s
|
||||
try dataStack.addStorageAndWait(SQLiteStore())
|
||||
```
|
||||
|
||||
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
|
||||
(The asynchronous variant of this method is explained further in the next section on [Migrations](#starting-migrations), and a reactive-programming variant in the section on [`DataStack` Combine publishers](#datastackreactive))
|
||||
|
||||
The file-related properties of `SQLiteStore` are actually requirements of another protocol that it implements, the `LocalStorage` protocol:
|
||||
```swift
|
||||
public protocol LocalStorage: StorageInterface {
|
||||
var fileURL: NSURL { get }
|
||||
@@ -463,6 +506,8 @@ let migrationProgress: Progress? = try dataStack.addStorage(
|
||||
```
|
||||
The `completion` block reports a `SetupResult` that indicates success or failure.
|
||||
|
||||
(A reactive-programming variant of this method is explained further in the section on [`DataStack` Combine publishers](#datastackreactive))
|
||||
|
||||
Notice that this method also returns an optional `Progress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the `"fractionCompleted"` key, or by using a closure-based utility exposed in *Progress+Convenience.swift*:
|
||||
```swift
|
||||
migrationProgress?.setProgressHandler { [weak self] (progress) -> Void in
|
||||
@@ -1460,6 +1505,8 @@ Note that the owner instance will not be retained. You may call `ObjectPublisher
|
||||
|
||||
The `ObjectSnapshot` returned from the `ObjectPublisher.snapshot` property returns a full-copy `struct` of all properties of the object. This is ideal for managing states as they are thread-safe and are not affected by further changes to the actual object. `ObjectPublisher` automatically updates its `snapshot` value to the latest state of the object.
|
||||
|
||||
(A reactive-programming variant of this method is explained in detail in the section on [`ObjectPublisher` Combine publishers](#objectpublisherreactive))
|
||||
|
||||
|
||||
### Observe a single object's per-property updates
|
||||
|
||||
@@ -1513,6 +1560,8 @@ Note that the owner instance will not be retained. You may call `ListPublisher.r
|
||||
|
||||
The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
|
||||
|
||||
(A reactive-programming variant of this method is explained in detail in the section on [`ListPublisher` Combine publishers](#listpublisherreactive))
|
||||
|
||||
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
|
||||
```swift
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
|
||||
@@ -1815,13 +1864,13 @@ Starting CoreStore 7.1.0, `CoreStoreObject` properties may be converted to `@Fie
|
||||
The `@Field.Stored` property wrapper is used for persisted value types. This is the replacement for "non-transient" `Value.Required` and `Value.Optional` properties.
|
||||
|
||||
<table>
|
||||
<tr><th>Before</th><th>`@Field.Stored`</th></tr>
|
||||
<tr><th>Before</th><th><pre lang=swift>@Field.Stored</pre></th></tr>
|
||||
<tr>
|
||||
<td><pre lang=swift>
|
||||
class Person: CoreStoreObject {
|
||||
<br />
|
||||
let title = Value.Required<String>("title", initial: "Mr.")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let title = Value.Required<String>("title", initial: "Mr.")
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
}
|
||||
</pre></td>
|
||||
<td><pre lang=swift>
|
||||
@@ -1846,20 +1895,20 @@ class Person: CoreStoreObject {
|
||||
The `@Field.Virtual` property wrapper is used for unsaved, computed value types. This is the replacement for "transient" `Value.Required` and `Value.Optional` properties.
|
||||
|
||||
<table>
|
||||
<tr><th>Before</th><th>`@Field.Virtual`</th></tr>
|
||||
<tr><th>Before</th><th><pre lang=swift>@Field.Virtual</pre></th></tr>
|
||||
<tr>
|
||||
<td><pre lang=swift>
|
||||
class Animal: CoreStoreObject {
|
||||
<br />
|
||||
let speciesPlural = Value.Required<String>(
|
||||
let speciesPlural = Value.Required<String>(
|
||||
"speciesPlural",
|
||||
transient: true,
|
||||
customGetter: Animal.getSpeciesPlural(_:)
|
||||
)
|
||||
<br />
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
let species = Value.Required<String>("species", initial: "")
|
||||
<br />
|
||||
static func getSpeciesPlural(_ partialObject: PartialObject<Animal>) -> String? {
|
||||
static func getSpeciesPlural(_ partialObject: PartialObject<Animal>) -> String? {
|
||||
let species = partialObject.value(for: { $0.species })
|
||||
return species + "s"
|
||||
}
|
||||
@@ -1894,12 +1943,12 @@ The `@Field.Coded` property wrapper is used for binary-codable values. This is t
|
||||
> ‼️ The current `Transformable.Required` and `Transformable.Optional` mechanism have no safe one-to-one conversion to `@Field.Coded`. Please use `@Field.Coded` only for newly added attributes.
|
||||
|
||||
<table>
|
||||
<tr><th>Before</th><th>`@Field.Coded`</th></tr>
|
||||
<tr><th>Before</th><th><pre lang=swift>@Field.Coded</pre></th></tr>
|
||||
<tr>
|
||||
<td><pre lang=swift>
|
||||
class Vehicle: CoreStoreObject {
|
||||
<br />
|
||||
let color = Transformable.Optional<UIColor>("color", initial: .white)
|
||||
let color = Transformable.Optional<UIColor>("color", initial: .white)
|
||||
}
|
||||
</pre></td>
|
||||
<td><pre lang=swift>
|
||||
@@ -1947,16 +1996,16 @@ The type of relationship is determined by the `@Field.Relationship` generic typ
|
||||
- `Set<T>` : To-many unordered relationship
|
||||
|
||||
<table>
|
||||
<tr><th>Before</th><th>`@Field.Stored`</th></tr>
|
||||
<tr><th>Before</th><th><pre lang=swift>@Field.Stored</pre></th></tr>
|
||||
<tr>
|
||||
<td><pre lang=swift>
|
||||
class Pet: CoreStoreObject {
|
||||
<br />
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
}
|
||||
class Person: CoreStoreObject {
|
||||
<br />
|
||||
let pets: Relationship.ToManyUnordered<Pet>("pets", inverse: \.$master)
|
||||
let pets: Relationship.ToManyUnordered<Pet>("pets", inverse: \.$master)
|
||||
}
|
||||
</pre></td>
|
||||
<td><pre lang=swift>
|
||||
@@ -1968,7 +2017,7 @@ class Pet: CoreStoreObject {
|
||||
class Person: CoreStoreObject {
|
||||
<br />
|
||||
@Field.Relationship("pets", inverse: \.$master)
|
||||
var pets: Set<Pet>
|
||||
var pets: Set<Pet>
|
||||
}
|
||||
</pre></td>
|
||||
</tr>
|
||||
@@ -2060,6 +2109,438 @@ Once the version lock is set, any changes in the properties or to the model will
|
||||
|
||||
<img width="700" alt="VersionLock failure" src="https://cloud.githubusercontent.com/assets/3029684/26525666/92f46f0c-4399-11e7-9395-4379f6f20876.png" />
|
||||
|
||||
## Reactive Programming
|
||||
### RxSwift
|
||||
RxSwift utilities are available through the [RxCoreStore](https://github.com/JohnEstropia/RxCoreStore) external module.
|
||||
|
||||
### Combine
|
||||
|
||||
Combine publishers are available from the `DataStack`, `ListPublisher`, and `ObjectPublisher`'s `.reactive` namespace property.
|
||||
|
||||
#### `DataStack.reactive`
|
||||
|
||||
Adding a storage through `DataStack.reactive.addStorage(_:)` returns a publisher that reports a `MigrationProgress` `enum` value. The `.migrating` value is only emitted if the storage goes through a migration. Refer to the [Setting up](#setting-up) section for details on the storage setup process itself.
|
||||
|
||||
```swift
|
||||
dataStack.reactive
|
||||
.addStorage(
|
||||
SQLiteStore(fileName: "core_data.sqlite")
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (progress) in
|
||||
print("\(round(progress.fractionCompleted * 100)) %") // 0.0 ~ 1.0
|
||||
switch progress {
|
||||
case .migrating(let storage, let nsProgress):
|
||||
// ...
|
||||
case .finished(let storage, let migrationRequired):
|
||||
// ...
|
||||
}
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
|
||||
[Transactions](#saving-and-processing-transactions) are also available as publishers through `DataStack.reactive.perform(_:)`, which returns a Combine `Future` that emits any type returned from the closure parameter:
|
||||
```swift
|
||||
dataStack.reactive
|
||||
.perform(
|
||||
asynchronous: { (transaction) -> (inserted: Set<NSManagedObject>, deleted: Set<NSManagedObject>) in
|
||||
|
||||
// ...
|
||||
return (
|
||||
transaction.insertedObjects(),
|
||||
transaction.deletedObjects()
|
||||
)
|
||||
}
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { value in
|
||||
let inserted = dataStack.fetchExisting(value0.inserted)
|
||||
let deleted = dataStack.fetchExisting(value0.deleted)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
|
||||
For importing convenience, `ImportableObject` and `ImportableUniqueObjects` can be imported directly through `DataStack.reactive.import[Unique]Object(_:source:)` and `DataStack.reactive.import[Unique]Objects(_:sourceArray:)` without having to create a transaction block. In this case the publisher emits objects that are already usable directly from the main queue:
|
||||
```swift
|
||||
dataStack.reactive
|
||||
.importUniqueObjects(
|
||||
Into<Person>(),
|
||||
sourceArray: [
|
||||
["name": "John"],
|
||||
["name": "Bob"],
|
||||
["name": "Joe"]
|
||||
]
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (people) in
|
||||
XCTAssertEqual(people?.count, 3)
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
|
||||
#### `ListPublisher.reactive`
|
||||
|
||||
`ListPublisher`s can be used to emit `ListSnapshot`s through Combine using `ListPublisher.reactive.snapshot(emitInitialValue:)`. The snapshot values are emitted in the main queue:
|
||||
```swift
|
||||
listPublisher.reactive
|
||||
.snapshot(emitInitialValue: true)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (listSnapshot) in
|
||||
dataSource.apply(
|
||||
listSnapshot,
|
||||
animatingDifferences: true
|
||||
)
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
|
||||
#### `ObjectPublisher.reactive`
|
||||
|
||||
`ObjectPublisher`s can be used to emit `ObjectSnapshot`s through Combine using `ObjectPublisher.reactive.snapshot(emitInitialValue:)`. The snapshot values are emitted in the main queue:
|
||||
```swift
|
||||
objectPublisher.reactive
|
||||
.snapshot(emitInitialValue: true)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (objectSnapshot) in
|
||||
tableViewCell.setObject(objectSnapshot)
|
||||
}
|
||||
)
|
||||
.store(in: &tableViewCell.cancellables)
|
||||
```
|
||||
|
||||
|
||||
## SwiftUI Utilities
|
||||
|
||||
Observing list and object changes in SwiftUI can be done through a couple of approaches. One is by creating [views that autoupdates their contents](#swiftui-views), or by declaring [property wrappers that trigger view updates](#swiftui-property-wrappers). Both approaches are implemented almost the same internally, but this lets you be flexible depending on the structure of your custom `View`s.
|
||||
|
||||
### SwiftUI Views
|
||||
|
||||
CoreStore provides `View` containers that automatically update their contents when data changes.
|
||||
|
||||
#### `ListReader`
|
||||
|
||||
A `ListReader` observes changes to a `ListPublisher` and creates its content views dynamically. The builder closure receives a `ListSnapshot` value that can be used to create the contents:
|
||||
```swift
|
||||
let people: ListPublisher<Person>
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ListReader(self.people) { listSnapshot in
|
||||
ForEach(objectIn: listSnapshot) { person in
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
.animation(.default)
|
||||
}
|
||||
```
|
||||
As shown above, a typical use case is to use it together with CoreStore's [`ForEach` extensions](#foreach).
|
||||
|
||||
A `KeyPath` can also be optionally provided to extract specific properties of the `ListSnapshot`:
|
||||
```swift
|
||||
let people: ListPublisher<Person>
|
||||
|
||||
var body: some View {
|
||||
ListReader(self.people, keyPath: \.count) { count in
|
||||
Text("Number of members: \(count)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `ObjectReader`
|
||||
|
||||
An `ObjectReader` observes changes to an `ObjectPublisher` and creates its content views dynamically. The builder closure receives an `ObjectSnapshot` value that can be used to create the contents:
|
||||
```swift
|
||||
let person: ObjectPublisher<Person>
|
||||
|
||||
var body: some View {
|
||||
ObjectReader(self.person) { objectSnapshot in
|
||||
// ...
|
||||
}
|
||||
.animation(.default)
|
||||
}
|
||||
```
|
||||
|
||||
A `KeyPath` can also be optionally provided to extract specific properties of the `ObjectSnapshot`:
|
||||
```swift
|
||||
let person: ObjectPublisher<Person>
|
||||
|
||||
var body: some View {
|
||||
ObjectReader(self.person, keyPath: \.fullName) { fullName in
|
||||
Text("Name: \(fullName)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By default, an `ObjectReader` does not create its views wheen the object observed is deleted from the store. In those cases, the `placeholder:` argument can be used to provide a custom `View` to display when the object is deleted:
|
||||
```swift
|
||||
let person: ObjectPublisher<Person>
|
||||
|
||||
var body: some View {
|
||||
ObjectReader(
|
||||
self.person,
|
||||
content: { objectSnapshot in
|
||||
// ...
|
||||
},
|
||||
placeholder: { Text("Record not found") }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### SwiftUI Property Wrappers
|
||||
|
||||
As an alternative to `ListReader` and `ObjectReader`, CoreStore also provides property wrappers that trigger view updates when the data changes.
|
||||
|
||||
#### `ListState`
|
||||
|
||||
A `@ListState` property exposes a `ListSnapshot` value that automatically updates to the latest changes.
|
||||
```swift
|
||||
@ListState
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
init(listPublisher: ListPublisher<Person>) {
|
||||
self._people = .init(listPublisher)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(objectIn: self.people) { objectSnapshot in
|
||||
// ...
|
||||
}
|
||||
}
|
||||
.animation(.default)
|
||||
}
|
||||
```
|
||||
As shown above, a typical use case is to use it together with CoreStore's [`ForEach` extensions](#foreach).
|
||||
|
||||
If a `ListPublisher` instance is not available yet, the fetch can be done inline by providing the fetch clauses and the `DataStack` instance. By doing so the property can be declared without an initial value:
|
||||
```swift
|
||||
@ListState(
|
||||
From<Person>()
|
||||
.sectionBy(\.age)
|
||||
.where(\.isMember == true)
|
||||
.orderBy(.ascending(\.lastName))
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(sectionIn: self.people) { section in
|
||||
Section(header: Text(section.sectionID)) {
|
||||
ForEach(objectIn: section) { person in
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.animation(.default)
|
||||
}
|
||||
```
|
||||
|
||||
For other initialization variants, refer to the *ListState.swift* source documentations.
|
||||
|
||||
|
||||
#### `ObjectState`
|
||||
|
||||
An `@ObjectState` property exposes an optional `ObjectSnapshot` value that automatically updates to the latest changes.
|
||||
```swift
|
||||
@ObjectState
|
||||
var person: ObjectSnapshot<Person>?
|
||||
|
||||
init(objectPublisher: ObjectPublisher<Person>) {
|
||||
self._person = .init(objectPublisher)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if let person = self.person {
|
||||
AsyncImage(person.$avatarURL)
|
||||
Text(person.$fullName)
|
||||
}
|
||||
else {
|
||||
Text("Record removed")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
As shown above, the property's value will be `nil` if the object has been deleted, so this can be used to display placeholders if needed.
|
||||
|
||||
### SwiftUI Extensions
|
||||
|
||||
For convenience, CoreStore provides extensions to the standard SwiftUI types.
|
||||
|
||||
#### `ForEach`
|
||||
|
||||
Several `ForEach` initializer overloads are available. Choose depending on your input data and the expected closure data. Refer to the table below (Take note of the argument labels as they are important):
|
||||
|
||||
<table>
|
||||
<tr><th>Data</th><th>Example</th></tr>
|
||||
<tr>
|
||||
<td>
|
||||
Signature:
|
||||
<pre lang=swift>
|
||||
ForEach(_: [ObjectSnapshot<O>])
|
||||
</pre>
|
||||
Closure:
|
||||
<pre lang=swift>
|
||||
ObjectSnapshot<O>
|
||||
</pre>
|
||||
</td>
|
||||
<td><pre lang=swift>
|
||||
let array: [ObjectSnapshot<Person>]
|
||||
<br />
|
||||
var body: some View {
|
||||
<br />
|
||||
List {
|
||||
<br />
|
||||
ForEach(self.array) { objectSnapshot in
|
||||
<br />
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Signature:
|
||||
<pre lang=swift>
|
||||
ForEach(objectIn: ListSnapshot<O>)
|
||||
</pre>
|
||||
Closure:
|
||||
<pre lang=swift>
|
||||
ObjectPublisher<O>
|
||||
</pre>
|
||||
</td>
|
||||
<td><pre lang=swift>
|
||||
let listSnapshot: ListSnapshot<Person>
|
||||
<br />
|
||||
var body: some View {
|
||||
<br />
|
||||
List {
|
||||
<br />
|
||||
ForEach(objectIn: self.listSnapshot) { objectPublisher in
|
||||
<br />
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Signature:
|
||||
<pre lang=swift>
|
||||
ForEach(objectIn: [ObjectSnapshot<O>])
|
||||
</pre>
|
||||
Closure:
|
||||
<pre lang=swift>
|
||||
ObjectPublisher<O>
|
||||
</pre>
|
||||
</td>
|
||||
<td><pre lang=swift>
|
||||
let array: [ObjectSnapshot<Person>]
|
||||
<br />
|
||||
var body: some View {
|
||||
<br />
|
||||
List {
|
||||
<br />
|
||||
ForEach(objectIn: self.array) { objectPublisher in
|
||||
<br />
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Signature:
|
||||
<pre lang=swift>
|
||||
ForEach(sectionIn: ListSnapshot<O>)
|
||||
</pre>
|
||||
Closure:
|
||||
<pre lang=swift>
|
||||
[ListSnapshot<O>.SectionInfo]
|
||||
</pre>
|
||||
</td>
|
||||
<td><pre lang=swift>
|
||||
let listSnapshot: ListSnapshot<Person>
|
||||
<br />
|
||||
var body: some View {
|
||||
<br />
|
||||
List {
|
||||
<br />
|
||||
ForEach(sectionIn: self.listSnapshot) { sectionInfo in
|
||||
<br />
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Signature:
|
||||
<pre lang=swift>
|
||||
ForEach(objectIn: ListSnapshot<O>.SectionInfo)
|
||||
</pre>
|
||||
Closure:
|
||||
<pre lang=swift>
|
||||
ObjectPublisher<O>
|
||||
</pre>
|
||||
</td>
|
||||
<td><pre lang=swift>
|
||||
let listSnapshot: ListSnapshot<Person>
|
||||
<br />
|
||||
var body: some View {
|
||||
<br />
|
||||
List {
|
||||
<br />
|
||||
ForEach(sectionIn: self.listSnapshot) { sectionInfo in
|
||||
<br />
|
||||
ForEach(objectIn: sectionInfo) { objectPublisher in
|
||||
<br />
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
# Roadmap
|
||||
|
||||
### Prototyping stage
|
||||
- [ ] Widget/Extensions storage-sharing support
|
||||
- [ ] CloudKit support
|
||||
|
||||
### Under consideration
|
||||
- [ ] Derived attributes
|
||||
- [ ] Cross-storage relationships (via Fetched Attributes)
|
||||
|
||||
# Installation
|
||||
- Requires:
|
||||
@@ -2074,7 +2555,7 @@ Once the version lock is set, any changes in the properties or to the model will
|
||||
### Install with CocoaPods
|
||||
In your `Podfile`, add
|
||||
```
|
||||
pod 'CoreStore', '~> 7.2'
|
||||
pod 'CoreStore', '~> 8.0'
|
||||
```
|
||||
and run
|
||||
```
|
||||
@@ -2085,7 +2566,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
|
||||
### Install with Carthage
|
||||
In your `Cartfile`, add
|
||||
```
|
||||
github "JohnEstropia/CoreStore" >= 7.3.0
|
||||
github "JohnEstropia/CoreStore" >= 8.0.0
|
||||
```
|
||||
and run
|
||||
```
|
||||
@@ -2096,7 +2577,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
|
||||
#### Install with Swift Package Manager:
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "7.3.1"))
|
||||
.package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "8.0.0"))
|
||||
]
|
||||
```
|
||||
Declare `import CoreStore` in your swift file to use the library.
|
||||
|
||||
@@ -30,7 +30,6 @@ import CoreData
|
||||
// MARK: - CSDataStack
|
||||
|
||||
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
|
||||
@available(macOS 10.12, *)
|
||||
extension CSDataStack {
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,6 @@ import CoreData
|
||||
|
||||
- SeeAlso: `ListMonitor`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public final class CSListMonitor: NSObject {
|
||||
|
||||
@@ -546,7 +545,6 @@ public final class CSListMonitor: NSObject {
|
||||
|
||||
// MARK: - ListMonitor
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListMonitor where ListMonitor.ObjectType: NSManagedObject {
|
||||
|
||||
// MARK: CoreStoreSwiftType
|
||||
|
||||
@@ -40,7 +40,6 @@ import CoreData
|
||||
|
||||
- SeeAlso: `ListObserver`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public protocol CSListObserver: AnyObject {
|
||||
|
||||
@@ -91,7 +90,6 @@ public protocol CSListObserver: AnyObject {
|
||||
|
||||
- SeeAlso: `ListObjectObserver`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public protocol CSListObjectObserver: CSListObserver {
|
||||
|
||||
@@ -152,7 +150,6 @@ public protocol CSListObjectObserver: CSListObserver {
|
||||
|
||||
- SeeAlso: `ListSectionObserver`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public protocol CSListSectionObserver: CSListObjectObserver {
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import CoreData
|
||||
|
||||
- SeeAlso: `ObjectMonitor`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public final class CSObjectMonitor: NSObject {
|
||||
|
||||
@@ -139,7 +138,6 @@ public final class CSObjectMonitor: NSObject {
|
||||
|
||||
// MARK: - ObjectMonitor
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ObjectMonitor where ObjectMonitor.ObjectType: NSManagedObject {
|
||||
|
||||
// MARK: CoreStoreSwiftType
|
||||
|
||||
@@ -38,7 +38,6 @@ import CoreData
|
||||
|
||||
- SeeAlso: `ObjectObserver`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public protocol CSObjectObserver: AnyObject {
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import CoreData
|
||||
|
||||
- SeeAlso: `SectionBy`
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
@objc
|
||||
public final class CSSectionBy: NSObject {
|
||||
|
||||
@@ -91,7 +90,6 @@ public final class CSSectionBy: NSObject {
|
||||
|
||||
// MARK: - SectionBy
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension SectionBy {
|
||||
|
||||
// MARK: CoreStoreSwiftType
|
||||
|
||||
@@ -359,7 +359,6 @@ extension UnsafeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStr
|
||||
|
||||
// MARK: - ListMonitor
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
@@ -404,7 +403,6 @@ fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConve
|
||||
private let numberOfObjects: Int
|
||||
}
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
@@ -431,7 +429,6 @@ extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvert
|
||||
|
||||
// MARK: - ListPublisher
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
@@ -614,7 +611,6 @@ extension MigrationType: CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: - ObjectMonitor
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ObjectMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
@@ -738,7 +734,6 @@ extension PartialObject: CustomDebugStringConvertible, CoreStoreDebugStringConve
|
||||
|
||||
// MARK: - SectionBy
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension SectionBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
|
||||
|
||||
// MARK: CustomDebugStringConvertible
|
||||
@@ -1259,33 +1254,20 @@ extension NSEntityDescription: CoreStoreDebugStringConvertible {
|
||||
|
||||
public var coreStoreDumpString: String {
|
||||
|
||||
var info: DumpInfo = [
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
("managedObjectClassName", self.managedObjectClassName!),
|
||||
("name", self.name as Any),
|
||||
("indexes", self.indexes),
|
||||
("isAbstract", self.isAbstract),
|
||||
("superentity?.name", self.superentity?.name as Any),
|
||||
("subentities", self.subentities.map({ $0.name })),
|
||||
("properties", self.properties),
|
||||
("uniquenessConstraints", self.uniquenessConstraints),
|
||||
("userInfo", self.userInfo as Any),
|
||||
("versionHash", self.versionHash),
|
||||
("versionHashModifier", self.versionHashModifier as Any),
|
||||
("renamingIdentifier", self.renamingIdentifier as Any)
|
||||
]
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
info.append(("indexes", self.indexes))
|
||||
}
|
||||
else {
|
||||
|
||||
info.append(("compoundIndexes", self.compoundIndexes))
|
||||
}
|
||||
if #available(macOS 10.11, iOS 9.0, *) {
|
||||
|
||||
info.append(("uniquenessConstraints", self.uniquenessConstraints))
|
||||
}
|
||||
return createFormattedString(
|
||||
"(", ")",
|
||||
info
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,18 +564,14 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
)
|
||||
}
|
||||
for (entity, entityDescription) in entityDescriptionsByEntity {
|
||||
|
||||
if entity.uniqueConstraints.contains(where: { !$0.isEmpty }) {
|
||||
|
||||
if #available(macOS 10.11, iOS 9.0, *) {
|
||||
|
||||
let uniqueConstraints = entity.uniqueConstraints.filter({ !$0.isEmpty })
|
||||
if !uniqueConstraints.isEmpty {
|
||||
|
||||
Internals.assert(
|
||||
entityDescription.superentity == nil,
|
||||
"Uniqueness constraints must be defined at the highest level possible."
|
||||
)
|
||||
entityDescription.uniquenessConstraints = entity.uniqueConstraints.map { $0.map { $0 as NSString } }
|
||||
}
|
||||
Internals.assert(
|
||||
entityDescription.superentity == nil,
|
||||
"Uniqueness constraints must be defined at the highest level possible."
|
||||
)
|
||||
entityDescription.uniquenessConstraints = entity.uniqueConstraints.map { $0.map { $0 as NSString } }
|
||||
}
|
||||
guard !entity.indexes.isEmpty else {
|
||||
|
||||
@@ -586,31 +582,18 @@ public final class CoreStoreSchema: DynamicSchema {
|
||||
entityDescription.coreStoreEntity = entity // reserialize
|
||||
}
|
||||
let attributesByName = entityDescription.attributesByName
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
|
||||
|
||||
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
|
||||
|
||||
return NSFetchIndexDescription.init(
|
||||
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
|
||||
elements: compoundIndexes.map { (keyPath) in
|
||||
|
||||
return NSFetchIndexElementDescription(
|
||||
property: attributesByName[keyPath]!,
|
||||
collationType: .binary
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
entityDescription.compoundIndexes = entity.indexes.map { (compoundIndexes) in
|
||||
|
||||
return compoundIndexes.map { (keyPath) in
|
||||
return NSFetchIndexDescription.init(
|
||||
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
|
||||
elements: compoundIndexes.map { (keyPath) in
|
||||
|
||||
return attributesByName[keyPath]!
|
||||
return NSFetchIndexElementDescription(
|
||||
property: attributesByName[keyPath]!,
|
||||
collationType: .binary
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,16 +613,7 @@ extension DataStack {
|
||||
}
|
||||
}
|
||||
let fileManager = FileManager.default
|
||||
let systemTemporaryDirectoryURL: URL
|
||||
if #available(macOS 10.12, iOS 10.0, *) {
|
||||
|
||||
systemTemporaryDirectoryURL = fileManager.temporaryDirectory
|
||||
}
|
||||
else {
|
||||
|
||||
systemTemporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||
}
|
||||
let temporaryDirectoryURL = systemTemporaryDirectoryURL
|
||||
let temporaryDirectoryURL = fileManager.temporaryDirectory
|
||||
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack")
|
||||
.appendingPathComponent(ProcessInfo().globallyUniqueString)
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import CoreData
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension DataStack {
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,6 +75,78 @@ extension DataStack.ReactiveNamespace {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `StorageInterface` to the stack.
|
||||
```
|
||||
dataStack.reactive
|
||||
.addStorage(
|
||||
InMemoryStore(configuration: "Config1")
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { storage in
|
||||
// ...
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- parameter storage: the storage
|
||||
- returns: A `Future` that emits a `StorageInterface` instance added to the `DataStack`. Note that the `StorageInterface` event value may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
|
||||
*/
|
||||
public func addStorage<T: StorageInterface>(_ storage: T) -> Future<T, CoreStoreError> {
|
||||
|
||||
return .init { (promise) in
|
||||
|
||||
self.base.addStorage(
|
||||
storage,
|
||||
completion: { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .success(let storage):
|
||||
promise(.success(storage))
|
||||
|
||||
case .failure(let error):
|
||||
promise(.failure(error))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. The event emits `DataStack.AddStoragePublisher.MigrationProgress` `enum` values.
|
||||
```
|
||||
dataStack.reactive
|
||||
.addStorage(
|
||||
SQLiteStore(
|
||||
fileName: "core_data.sqlite",
|
||||
configuration: "Config1"
|
||||
)
|
||||
)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
// ...
|
||||
},
|
||||
receiveValue: { (progress) in
|
||||
print("\(round(progress.fractionCompleted * 100)) %") // 0.0 ~ 1.0
|
||||
}
|
||||
)
|
||||
.store(in: &cancellables)
|
||||
```
|
||||
- parameter storage: the local storage
|
||||
- returns: A `DataStack.AddStoragePublisher` that emits a `DataStack.AddStoragePublisher.MigrationProgress` value with metadata for migration progress. Note that the `LocalStorage` event value may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
|
||||
*/
|
||||
public func addStorage<T: LocalStorage>(_ storage: T) -> DataStack.AddStoragePublisher<T> {
|
||||
|
||||
return .init(
|
||||
dataStack: self.base,
|
||||
storage: storage
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Reactive extension for `CoreStore.DataStack`'s `importObject(...)` API. Creates an `ImportableObject` by importing from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
|
||||
```
|
||||
|
||||
239
Sources/DataStack.AddStoragePublisher.swift
Normal file
239
Sources/DataStack.AddStoragePublisher.swift
Normal file
@@ -0,0 +1,239 @@
|
||||
//
|
||||
// DataStack.AddStoragePublisher.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2021 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension DataStack {
|
||||
|
||||
// MARK: - AddStoragePublisher
|
||||
|
||||
/**
|
||||
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
|
||||
|
||||
- SeeAlso: DataStack.reactive.addStorage(_:)
|
||||
*/
|
||||
public struct AddStoragePublisher<Storage: LocalStorage>: Publisher {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let dataStack: DataStack
|
||||
internal let storage: Storage
|
||||
|
||||
|
||||
// MARK: Publisher
|
||||
|
||||
public typealias Output = MigrationProgress
|
||||
public typealias Failure = CoreStoreError
|
||||
|
||||
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
|
||||
|
||||
subscriber.receive(
|
||||
subscription: AddStorageSubscription(
|
||||
dataStack: self.dataStack,
|
||||
storage: self.storage,
|
||||
subscriber: subscriber
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - MigrationProgress
|
||||
|
||||
/**
|
||||
A `MigrationProgress` contains info on a `LocalStorage`'s setup progress.
|
||||
|
||||
- SeeAlso: DataStack.reactive.addStorage(_:)
|
||||
*/
|
||||
public enum MigrationProgress {
|
||||
|
||||
/**
|
||||
The `LocalStorage` is currently being migrated
|
||||
*/
|
||||
case migrating(storage: Storage, progressObject: Progress)
|
||||
|
||||
/**
|
||||
The `LocalStorage` has been added to the `DataStack` and is ready for reading and writing
|
||||
*/
|
||||
case finished(storage: Storage, migrationRequired: Bool)
|
||||
|
||||
/**
|
||||
The fraction of the overall work completed by the migration. Returns a value between 0.0 and 1.0, inclusive.
|
||||
*/
|
||||
public var fractionCompleted: Double {
|
||||
|
||||
switch self {
|
||||
|
||||
case .migrating(_, let progressObject):
|
||||
return progressObject.fractionCompleted
|
||||
|
||||
case .finished:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the storage was successfully added to the stack, `false` otherwise.
|
||||
*/
|
||||
public var isCompleted: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .migrating:
|
||||
return false
|
||||
|
||||
case .finished:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - AddStorageSubscriber
|
||||
|
||||
fileprivate final class AddStorageSubscriber: Subscriber {
|
||||
|
||||
// MARK: Subscriber
|
||||
|
||||
typealias Failure = CoreStoreError
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Output) -> Subscribers.Demand {
|
||||
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - AddStorageSubscription
|
||||
|
||||
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == CoreStoreError {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate init(
|
||||
dataStack: DataStack,
|
||||
storage: Storage,
|
||||
subscriber: S
|
||||
) {
|
||||
|
||||
self.dataStack = dataStack
|
||||
self.storage = storage
|
||||
self.subscriber = subscriber
|
||||
}
|
||||
|
||||
|
||||
// MARK: Subscription
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
|
||||
guard demand > 0 else {
|
||||
|
||||
return
|
||||
}
|
||||
var progress: Progress? = nil
|
||||
progress = self.dataStack.addStorage(
|
||||
self.storage,
|
||||
completion: { [weak self] result in
|
||||
|
||||
progress?.setProgressHandler(nil)
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
let subscriber = self.subscriber
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
switch result {
|
||||
|
||||
case .success(let storage):
|
||||
_ = subscriber.receive(
|
||||
.finished(
|
||||
storage: storage,
|
||||
migrationRequired: progress != nil
|
||||
)
|
||||
)
|
||||
subscriber.receive(
|
||||
completion: .finished
|
||||
)
|
||||
|
||||
case .failure(let error):
|
||||
subscriber.receive(
|
||||
completion: .failure(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
if let progress = progress {
|
||||
|
||||
progress.cs_setProgressHandler { [weak self] progress in
|
||||
|
||||
guard
|
||||
let self = self,
|
||||
let subscriber = self.subscriber
|
||||
else {
|
||||
|
||||
return
|
||||
}
|
||||
_ = subscriber.receive(
|
||||
.migrating(
|
||||
storage: self.storage,
|
||||
progressObject: progress
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cancellable
|
||||
|
||||
func cancel() {
|
||||
|
||||
self.subscriber = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let dataStack: DataStack
|
||||
private let storage: Storage
|
||||
private var subscriber: S?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -247,16 +247,7 @@ extension DiffableDataSource {
|
||||
|
||||
return
|
||||
}
|
||||
if #available(iOS 11.0, tvOS 11.0, *) {
|
||||
|
||||
base.performBatchUpdates(updates)
|
||||
}
|
||||
else {
|
||||
|
||||
base.beginUpdates()
|
||||
updates()
|
||||
base.endUpdates()
|
||||
}
|
||||
base.performBatchUpdates(updates)
|
||||
}
|
||||
|
||||
public func reloadData() {
|
||||
|
||||
@@ -70,7 +70,6 @@ public final class Entity<O: CoreStoreObject>: DynamicEntity {
|
||||
- parameter indexes: the compound indexes for the entity as an array of arrays. The arrays contained in the returned array contain `KeyPath`s to properties of the entity.
|
||||
- parameter uniqueConstraints: sets uniqueness constraints for the entity. A uniqueness constraint is a set of one or more `KeyPath`s whose value must be unique over the set of instances of that entity. This value forms part of the entity's version hash. Uniqueness constraint violations can be computationally expensive to handle. It is highly suggested that there be only one uniqueness constraint per entity hierarchy. Uniqueness constraints must be defined at the highest level possible, and CoreStore will raise an assertion failure if unique constraints are added to a sub entity.
|
||||
*/
|
||||
@available(macOS 10.11, *)
|
||||
public convenience init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil, indexes: [[PartialKeyPath<O>]] = [], uniqueConstraints: [[PartialKeyPath<O>]]) {
|
||||
|
||||
self.init(
|
||||
@@ -116,7 +115,6 @@ public final class Entity<O: CoreStoreObject>: DynamicEntity {
|
||||
- parameter indexes: the compound indexes for the entity as an array of arrays. The arrays contained in the returned array contain KeyPath's to properties of the entity.
|
||||
- parameter uniqueConstraints: sets uniqueness constraints for the entity. A uniqueness constraint is a set of one or more `KeyPath`s whose value must be unique over the set of instances of that entity. This value forms part of the entity's version hash. Uniqueness constraint violations can be computationally expensive to handle. It is highly suggested that there be only one uniqueness constraint per entity hierarchy. Uniqueness constraints must be defined at the highest level possible, and CoreStore will raise an assertion failure if unique constraints are added to a sub entity.
|
||||
*/
|
||||
@available(macOS 10.11, *)
|
||||
public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil, indexes: [[PartialKeyPath<O>]] = [], uniqueConstraints: [[PartialKeyPath<O>]]) {
|
||||
|
||||
let meta = O.meta
|
||||
|
||||
@@ -47,17 +47,10 @@ extension FieldCoders {
|
||||
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
return try! NSKeyedArchiver.archivedData(
|
||||
withRootObject: fieldValue,
|
||||
requiringSecureCoding: self.requiresSecureCoding
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
return NSKeyedArchiver.archivedData(withRootObject: fieldValue)
|
||||
}
|
||||
return try! NSKeyedArchiver.archivedData(
|
||||
withRootObject: fieldValue,
|
||||
requiringSecureCoding: self.requiresSecureCoding
|
||||
)
|
||||
}
|
||||
|
||||
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
|
||||
@@ -66,14 +59,10 @@ extension FieldCoders {
|
||||
|
||||
return nil
|
||||
}
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
return try! NSKeyedUnarchiver.unarchivedObject(ofClass: FieldStoredValue.self, from: data)
|
||||
}
|
||||
else {
|
||||
|
||||
return NSKeyedUnarchiver.unarchiveObject(with: data) as! FieldStoredValue?
|
||||
}
|
||||
return try! NSKeyedUnarchiver.unarchivedObject(
|
||||
ofClass: FieldStoredValue.self,
|
||||
from: data
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -35,11 +35,40 @@ import SwiftUI
|
||||
extension ForEach where Content: View {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Creates an instance that creates views for each object in a collection of `ObjectSnapshot`s. The objects' `NSManagedObjectID` are used as the identifier
|
||||
```
|
||||
let people: [ObjectSnapshot<Person>]
|
||||
|
||||
var body: some View {
|
||||
|
||||
List {
|
||||
|
||||
ForEach(self.people) { person in
|
||||
|
||||
ProfileView(person)
|
||||
}
|
||||
}
|
||||
.animation(.default)
|
||||
}
|
||||
```
|
||||
|
||||
- parameter objectSnapshots: The collection of `ObjectSnapshot`s that the `ForEach` instance uses to create views dynamically
|
||||
- parameter content: The view builder that receives an `ObjectPublisher` instance and creates views dynamically.
|
||||
*/
|
||||
public init<O: DynamicObject>(
|
||||
_ objectSnapshots: Data,
|
||||
@ViewBuilder content: @escaping (ObjectSnapshot<O>) -> Content
|
||||
) where Data.Element == ObjectSnapshot<O>, ID == O.ObjectID {
|
||||
|
||||
self.init(objectSnapshots, id: \.cs_objectID, content: content)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an instance that creates views for each object in a `ListSnapshot`.
|
||||
```
|
||||
@LiveList
|
||||
@ListState
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
var body: some View {
|
||||
@@ -98,7 +127,7 @@ extension ForEach where Content: View {
|
||||
/**
|
||||
Creates an instance that creates views for `ListSnapshot` sections.
|
||||
```
|
||||
@LiveList
|
||||
@ListState
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
var body: some View {
|
||||
@@ -135,7 +164,7 @@ extension ForEach where Content: View {
|
||||
/**
|
||||
Creates an instance that creates views for each object in a `ListSnapshot.SectionInfo`.
|
||||
```
|
||||
@LiveList
|
||||
@ListState
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -221,7 +221,6 @@ extension From {
|
||||
- parameter clause: the `SectionBy` to be used by the `ListMonitor`
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy(_ clause: SectionBy<O>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return .init(
|
||||
@@ -237,7 +236,6 @@ extension From {
|
||||
- parameter sectionKeyPath: the key path to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy(_ sectionKeyPath: KeyPathString) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(sectionKeyPath, sectionIndexTransformer: { _ in nil })
|
||||
@@ -251,7 +249,6 @@ extension From {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy(
|
||||
_ sectionKeyPath: KeyPathString,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -284,7 +281,6 @@ extension From {
|
||||
// MARK: Deprecated
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy(
|
||||
_ sectionKeyPath: KeyPathString,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -319,7 +315,6 @@ extension From where O: NSManagedObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, T>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -336,7 +331,6 @@ extension From where O: NSManagedObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, T>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -352,7 +346,6 @@ extension From where O: NSManagedObject {
|
||||
// MARK: Deprecated
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, T>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -431,7 +424,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -446,7 +438,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -461,7 +452,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -476,7 +466,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -491,7 +480,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -506,7 +494,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -521,7 +508,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
|
||||
|
||||
return self.sectionBy(
|
||||
@@ -538,7 +524,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -558,7 +543,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -578,7 +562,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -598,7 +581,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -618,7 +600,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -638,7 +619,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -658,7 +638,6 @@ extension From where O: CoreStoreObject {
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
|
||||
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
|
||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -674,7 +653,6 @@ extension From where O: CoreStoreObject {
|
||||
// MARK: Deprecated
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -687,7 +665,6 @@ extension From where O: CoreStoreObject {
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -700,7 +677,6 @@ extension From where O: CoreStoreObject {
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -713,7 +689,6 @@ extension From where O: CoreStoreObject {
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -726,7 +701,6 @@ extension From where O: CoreStoreObject {
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -739,7 +713,6 @@ extension From where O: CoreStoreObject {
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -752,7 +725,6 @@ extension From where O: CoreStoreObject {
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
|
||||
@available(macOS 10.12, *)
|
||||
public func sectionBy<T>(
|
||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
|
||||
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
||||
@@ -1224,7 +1196,6 @@ extension QueryChainBuilder where O: CoreStoreObject {
|
||||
|
||||
// MARK: - SectionMonitorChainBuilder
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension SectionMonitorChainBuilder {
|
||||
|
||||
/**
|
||||
@@ -1376,7 +1347,6 @@ extension SectionMonitorChainBuilder {
|
||||
|
||||
// MARK: - SectionMonitorChainBuilder where O: CoreStoreObject
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension SectionMonitorChainBuilder where O: CoreStoreObject {
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,27 +77,42 @@ extension Internals {
|
||||
let transformerName = self.transformerName
|
||||
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
|
||||
|
||||
if transformerName == .secureUnarchiveFromDataTransformerName {
|
||||
switch transformerName {
|
||||
|
||||
case .secureUnarchiveFromDataTransformerName,
|
||||
.isNotNilTransformerName,
|
||||
.isNilTransformerName,
|
||||
.negateBooleanTransformerName:
|
||||
return
|
||||
|
||||
case let transformerName:
|
||||
Self.cachedCoders[transformerName] = self
|
||||
|
||||
Foundation.ValueTransformer.setValueTransformer(
|
||||
self.transformer,
|
||||
forName: transformerName
|
||||
)
|
||||
}
|
||||
}
|
||||
switch transformerName {
|
||||
else {
|
||||
|
||||
switch transformerName {
|
||||
|
||||
case .keyedUnarchiveFromDataTransformerName,
|
||||
.unarchiveFromDataTransformerName,
|
||||
.isNotNilTransformerName,
|
||||
.isNilTransformerName,
|
||||
.negateBooleanTransformerName:
|
||||
return
|
||||
case .keyedUnarchiveFromDataTransformerName,
|
||||
.unarchiveFromDataTransformerName,
|
||||
.isNotNilTransformerName,
|
||||
.isNilTransformerName,
|
||||
.negateBooleanTransformerName:
|
||||
return
|
||||
|
||||
case let transformerName:
|
||||
Self.cachedCoders[transformerName] = self
|
||||
case let transformerName:
|
||||
Self.cachedCoders[transformerName] = self
|
||||
|
||||
Foundation.ValueTransformer.setValueTransformer(
|
||||
self.transformer,
|
||||
forName: transformerName
|
||||
)
|
||||
Foundation.ValueTransformer.setValueTransformer(
|
||||
self.transformer,
|
||||
forName: transformerName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,22 +68,8 @@ extension Internals {
|
||||
}
|
||||
set {
|
||||
|
||||
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
|
||||
|
||||
self.copiedAffectedStores = (newValue as NSArray?)?.copy() as! NSArray?
|
||||
super.affectedStores = newValue
|
||||
return
|
||||
}
|
||||
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
|
||||
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
|
||||
if let releaseArray = self.releaseArray {
|
||||
|
||||
releaseArray.release()
|
||||
self.releaseArray = nil
|
||||
}
|
||||
self.copiedAffectedStores = (newValue as NSArray?)?.copy() as! NSArray?
|
||||
super.affectedStores = newValue
|
||||
self.releaseArray = (super.affectedStores as NSArray?).map(Unmanaged<NSArray>.passRetained(_:))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ extension Internals {
|
||||
|
||||
// MARK: - CoreStoreFetchedResultsController
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject> {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -29,7 +29,6 @@ import CoreData
|
||||
|
||||
// MARK: - FetchedResultsControllerHandler
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
internal protocol FetchedResultsControllerHandler: AnyObject {
|
||||
|
||||
var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? { get }
|
||||
@@ -50,7 +49,6 @@ extension Internals {
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: Internal
|
||||
@@ -90,14 +88,6 @@ extension Internals {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
|
||||
else {
|
||||
|
||||
self.deletedSections = []
|
||||
self.insertedSections = []
|
||||
}
|
||||
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@@ -123,102 +113,11 @@ extension Internals {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: type,
|
||||
newIndexPath: newIndexPath
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
guard var actualType = NSFetchedResultsChangeType(rawValue: type.rawValue) else {
|
||||
|
||||
// This fix is for a bug where iOS passes 0 for NSFetchedResultsChangeType, but this is not a valid enum case.
|
||||
// Swift will then always execute the first case of the switch causing strange behaviour.
|
||||
// https://forums.developer.apple.com/thread/12184#31850
|
||||
return
|
||||
}
|
||||
|
||||
// This whole dance is a workaround for a nasty bug introduced in XCode 7 targeted at iOS 8 devices
|
||||
// http://stackoverflow.com/questions/31383760/ios-9-attempt-to-delete-and-reload-the-same-index-path/31384014#31384014
|
||||
// https://forums.developer.apple.com/message/9998#9998
|
||||
// https://forums.developer.apple.com/message/31849#31849
|
||||
|
||||
if case .update = actualType,
|
||||
indexPath != nil,
|
||||
newIndexPath != nil {
|
||||
|
||||
actualType = .move
|
||||
}
|
||||
|
||||
switch actualType {
|
||||
|
||||
case .update:
|
||||
guard let section = indexPath?[0] else {
|
||||
|
||||
return
|
||||
}
|
||||
if self.deletedSections.contains(section)
|
||||
|| self.insertedSections.contains(section) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case .move:
|
||||
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {
|
||||
|
||||
return
|
||||
}
|
||||
guard indexPath == newIndexPath else {
|
||||
|
||||
break
|
||||
}
|
||||
if self.insertedSections.contains(indexPath[0]) {
|
||||
|
||||
// Observers that handle the .Move change are advised to delete then reinsert the object instead of just moving. This is especially true when indexPath and newIndexPath are equal. For example, calling tableView.moveRowAtIndexPath(_:toIndexPath) when both indexPaths are the same will crash the tableView.
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: .move,
|
||||
newIndexPath: newIndexPath
|
||||
)
|
||||
return
|
||||
}
|
||||
if self.deletedSections.contains(indexPath[0]) {
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: nil,
|
||||
forChangeType: .insert,
|
||||
newIndexPath: indexPath
|
||||
)
|
||||
return
|
||||
}
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: .update,
|
||||
newIndexPath: nil
|
||||
)
|
||||
return
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: actualType,
|
||||
forChangeType: type,
|
||||
newIndexPath: newIndexPath
|
||||
)
|
||||
}
|
||||
@@ -230,18 +129,6 @@ extension Internals {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
|
||||
else {
|
||||
|
||||
switch type {
|
||||
|
||||
case .delete: self.deletedSections.insert(sectionIndex)
|
||||
case .insert: self.insertedSections.insert(sectionIndex)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeSection: sectionInfo,
|
||||
@@ -255,14 +142,5 @@ extension Internals {
|
||||
|
||||
return self.handler?.sectionIndexTransformer(sectionName)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private var deletedSections = Set<Int>()
|
||||
|
||||
@nonobjc
|
||||
private var insertedSections = Set<Int>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ import CoreData
|
||||
```
|
||||
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
// MARK: Public (Accessors)
|
||||
@@ -1178,7 +1177,6 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
|
||||
|
||||
// MARK: - ListMonitor where O: NSManagedObject
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListMonitor where O: NSManagedObject {
|
||||
|
||||
/**
|
||||
@@ -1236,7 +1234,6 @@ extension ListMonitor where O: NSManagedObject {
|
||||
|
||||
// MARK: - ListMonitor where O: CoreStoreObject
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListMonitor where O: CoreStoreObject {
|
||||
|
||||
/**
|
||||
@@ -1288,7 +1285,6 @@ extension ListMonitor where O: CoreStoreObject {
|
||||
|
||||
// MARK: - ListMonitor: FetchedResultsControllerHandler
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
@@ -1402,7 +1398,6 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: - Notification Keys
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension Notification.Name {
|
||||
|
||||
fileprivate static let listMonitorWillChangeList = Notification.Name(rawValue: "listMonitorWillChangeList")
|
||||
|
||||
@@ -39,7 +39,6 @@ import CoreData
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public protocol ListObserver: AnyObject {
|
||||
|
||||
/**
|
||||
@@ -82,7 +81,6 @@ public protocol ListObserver: AnyObject {
|
||||
|
||||
// MARK: - ListObserver (Default Implementations)
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListObserver {
|
||||
|
||||
public func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>) { }
|
||||
@@ -103,7 +101,6 @@ extension ListObserver {
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public protocol ListObjectObserver: ListObserver {
|
||||
|
||||
/**
|
||||
@@ -151,7 +148,6 @@ public protocol ListObjectObserver: ListObserver {
|
||||
|
||||
// MARK: - ListObjectObserver (Default Implementations)
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListObjectObserver {
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) { }
|
||||
@@ -177,7 +173,6 @@ extension ListObjectObserver {
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public protocol ListSectionObserver: ListObjectObserver {
|
||||
|
||||
/**
|
||||
@@ -204,7 +199,6 @@ public protocol ListSectionObserver: ListObjectObserver {
|
||||
|
||||
// MARK: - ListSectionObserver (Default Implementations)
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ListSectionObserver {
|
||||
|
||||
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
|
||||
|
||||
@@ -37,6 +37,8 @@ extension ListPublisher {
|
||||
|
||||
/**
|
||||
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
|
||||
|
||||
- SeeAlso: ListPublisher.reactive.snapshot(emitInitialValue:)
|
||||
*/
|
||||
public struct SnapshotPublisher: Publisher {
|
||||
|
||||
|
||||
@@ -25,16 +25,6 @@
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
#endif
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
import SwiftUI
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - ListPublisher
|
||||
|
||||
@@ -64,7 +54,6 @@ import SwiftUI
|
||||
```
|
||||
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
|
||||
// MARK: Public (Accessors)
|
||||
@@ -89,13 +78,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
*/
|
||||
public fileprivate(set) var snapshot: ListSnapshot<O> = .init() {
|
||||
|
||||
willSet {
|
||||
|
||||
self.willChange()
|
||||
}
|
||||
didSet {
|
||||
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
}
|
||||
}
|
||||
@@ -315,11 +299,6 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
|
||||
self.observers.removeAllObjects()
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let rawObjectWillChange: Any?
|
||||
|
||||
|
||||
// MARK: Private
|
||||
@@ -375,21 +354,6 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
|
||||
applyFetchClauses: applyFetchClauses
|
||||
)
|
||||
|
||||
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
|
||||
#if canImport(Combine)
|
||||
self.rawObjectWillChange = ObservableObjectPublisher()
|
||||
|
||||
#else
|
||||
self.rawObjectWillChange = nil
|
||||
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
|
||||
self.rawObjectWillChange = nil
|
||||
}
|
||||
|
||||
self.fetchedResultsControllerDelegate.handler = self
|
||||
|
||||
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
||||
@@ -428,143 +392,3 @@ extension ListPublisher: FetchedDiffableDataSourceSnapshotHandler {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension ListPublisher: ObservableObject {}
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension ListPublisher: Publisher {
|
||||
|
||||
// MARK: Publisher
|
||||
|
||||
public typealias Output = ListSnapshot<O>
|
||||
public typealias Failure = Never
|
||||
|
||||
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
|
||||
|
||||
subscriber.receive(
|
||||
subscription: ListSnapshotSubscription(
|
||||
publisher: self,
|
||||
subscriber: subscriber
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSnapshotSubscriber
|
||||
|
||||
fileprivate final class ListSnapshotSubscriber: Subscriber {
|
||||
|
||||
// MARK: Subscriber
|
||||
|
||||
typealias Failure = Never
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Output) -> Subscribers.Demand {
|
||||
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSnapshotSubscription
|
||||
|
||||
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
init(publisher: ListPublisher<O>, subscriber: S) {
|
||||
|
||||
self.publisher = publisher
|
||||
self.subscriber = subscriber
|
||||
}
|
||||
|
||||
|
||||
// MARK: Subscription
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
|
||||
guard demand > 0 else {
|
||||
|
||||
return
|
||||
}
|
||||
self.publisher.addObserver(self) { [weak self] (publisher) in
|
||||
|
||||
guard let self = self, let subscriber = self.subscriber else {
|
||||
|
||||
return
|
||||
}
|
||||
_ = subscriber.receive(publisher.snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cancellable
|
||||
|
||||
func cancel() {
|
||||
self.publisher.removeObserver(self)
|
||||
self.subscriber = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let publisher: ListPublisher<O>
|
||||
private var subscriber: S?
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - ListPublisher
|
||||
|
||||
extension ListPublisher {
|
||||
|
||||
// MARK: ObservableObject
|
||||
|
||||
#if canImport(Combine)
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
|
||||
return self.rawObjectWillChange! as! ObservableObjectPublisher
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
fileprivate func willChange() {
|
||||
|
||||
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) else {
|
||||
|
||||
return
|
||||
}
|
||||
#if canImport(Combine)
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
withAnimation {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
self.objectWillChange.send()
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
fileprivate func didChange() {
|
||||
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
|
||||
self._list = .init(listPublisher)
|
||||
self.content = content
|
||||
self.keyPath = \.self
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,8 +97,10 @@ public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
) {
|
||||
|
||||
self._list = .init(listPublisher)
|
||||
self.content = content
|
||||
self.keyPath = keyPath
|
||||
self.content = {
|
||||
|
||||
content($0[keyPath: keyPath])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,17 +108,16 @@ public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
|
||||
public var body: some View {
|
||||
|
||||
self.content(self.list[keyPath: self.keyPath])
|
||||
self.content(self.list)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@LiveList
|
||||
@ListState
|
||||
private var list: ListSnapshot<Object>
|
||||
|
||||
private let content: (Value) -> Content
|
||||
private let keyPath: KeyPath<ListSnapshot<Object>, Value>
|
||||
private let content: (ListSnapshot<Object>) -> Content
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// LiveList.swift
|
||||
// ListState.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2021 John Rommel Estropia
|
||||
@@ -29,21 +29,21 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
|
||||
// MARK: - LiveList
|
||||
// MARK: - ListState
|
||||
|
||||
/**
|
||||
A property wrapper type that can read `ListPublisher` changes.
|
||||
*/
|
||||
@propertyWrapper
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
public struct ListState<Object: DynamicObject>: DynamicProperty {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Creates an instance that observes `ListPublisher` changes and exposes a `ListSnapshot` value.
|
||||
```
|
||||
@LiveList
|
||||
@ListState
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
init(listPublisher: ListPublisher<Person>) {
|
||||
@@ -64,7 +64,7 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
}
|
||||
```
|
||||
|
||||
- parameter listPublisher: The `ListPublisher` that the `LiveList` will observe changes for
|
||||
- parameter listPublisher: The `ListPublisher` that the `ListState` will observe changes for
|
||||
*/
|
||||
public init(
|
||||
_ listPublisher: ListPublisher<Object>
|
||||
@@ -76,10 +76,11 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
/**
|
||||
Creates an instance that observes the specified `FetchChainableBuilderType` and exposes a `ListSnapshot` value.
|
||||
```
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Person>()
|
||||
.where(\.isMember == true)
|
||||
.orderBy(.ascending(\.lastName))
|
||||
.orderBy(.ascending(\.lastName)),
|
||||
in: Globals.dataStack
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
@@ -109,11 +110,12 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
/**
|
||||
Creates an instance that observes the specified `SectionMonitorBuilderType` and exposes a `ListSnapshot` value.
|
||||
```
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Person>()
|
||||
.sectionBy(\.age)
|
||||
.where(\.isMember == true)
|
||||
.orderBy(.ascending(\.lastName))
|
||||
.orderBy(.ascending(\.lastName)),
|
||||
in: Globals.dataStack
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
@@ -149,10 +151,11 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
/**
|
||||
Creates an instance that observes the specified `From` and `FetchClause`s and exposes a `ListSnapshot` value.
|
||||
```
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Person>(),
|
||||
Where<Person>(\.isMember == true),
|
||||
OrderBy<Person>(.ascending(\.lastName))
|
||||
OrderBy<Person>(.ascending(\.lastName)),
|
||||
in: Globals.dataStack
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
@@ -184,12 +187,13 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
/**
|
||||
Creates an instance that observes the specified `From` and `FetchClause`s and exposes a `ListSnapshot` value.
|
||||
```
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Person>(),
|
||||
[
|
||||
Where<Person>(\.isMember == true),
|
||||
OrderBy<Person>(.ascending(\.lastName))
|
||||
]
|
||||
],
|
||||
in: Globals.dataStack
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
@@ -221,11 +225,12 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
/**
|
||||
Creates an instance that observes the specified `From`, `SectionBy`, and `FetchClause`s and exposes a sectioned `ListSnapshot` value.
|
||||
```
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Person>(),
|
||||
SectionBy(\.age),
|
||||
Where<Person>(\.isMember == true),
|
||||
OrderBy<Person>(.ascending(\.lastName))
|
||||
OrderBy<Person>(.ascending(\.lastName)),
|
||||
in: Globals.dataStack
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
@@ -265,13 +270,14 @@ public struct LiveList<Object: DynamicObject>: DynamicProperty {
|
||||
/**
|
||||
Creates an instance that observes the specified `From`, `SectionBy`, and `FetchClause`s and exposes a sectioned `ListSnapshot` value.
|
||||
```
|
||||
@LiveList(
|
||||
@ListState(
|
||||
From<Person>(),
|
||||
SectionBy(\.age),
|
||||
[
|
||||
Where<Person>(\.isMember == true),
|
||||
OrderBy<Person>(.ascending(\.lastName))
|
||||
]
|
||||
],
|
||||
in: Globals.dataStack
|
||||
)
|
||||
var people: ListSnapshot<Person>
|
||||
|
||||
@@ -29,7 +29,6 @@ import CoreData
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension DataStack {
|
||||
|
||||
/**
|
||||
@@ -114,7 +113,6 @@ extension DataStack {
|
||||
|
||||
// MARK: - UnsafeDataTransaction
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension UnsafeDataTransaction {
|
||||
|
||||
/**
|
||||
@@ -204,7 +202,6 @@ extension Internals {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
fileprivate static func createFRC<O: NSManagedObject>(fromContext context: NSManagedObjectContext, from: From<O>, sectionBy: SectionBy<O>? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController<O> {
|
||||
|
||||
let controller = Internals.CoreStoreFetchedResultsController(
|
||||
|
||||
@@ -230,14 +230,7 @@ extension NSManagedObjectContext {
|
||||
@nonobjc
|
||||
internal func refreshAndMergeAllObjects() {
|
||||
|
||||
if #available(iOS 8.3, macOS 10.11, *) {
|
||||
|
||||
self.refreshAllObjects()
|
||||
}
|
||||
else {
|
||||
|
||||
self.registeredObjects.forEach { self.refresh($0, mergeChanges: true) }
|
||||
}
|
||||
self.refreshAllObjects()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import CoreData
|
||||
|
||||
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentation {
|
||||
|
||||
/**
|
||||
@@ -376,7 +375,6 @@ public final class ObjectMonitor<O: DynamicObject>: Hashable, ObjectRepresentati
|
||||
|
||||
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
@@ -426,7 +424,6 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: - Notification.Name
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension Notification.Name {
|
||||
|
||||
fileprivate static let objectMonitorWillChangeObject = Notification.Name(rawValue: "objectMonitorWillChangeObject")
|
||||
|
||||
@@ -36,7 +36,6 @@ import CoreData
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public protocol ObjectObserver: AnyObject {
|
||||
|
||||
/**
|
||||
@@ -76,7 +75,6 @@ public protocol ObjectObserver: AnyObject {
|
||||
|
||||
// MARK: - ObjectObserver (Default Implementations)
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension ObjectObserver {
|
||||
|
||||
public func objectMonitor(_ monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType) { }
|
||||
|
||||
@@ -37,6 +37,8 @@ extension ObjectPublisher {
|
||||
|
||||
/**
|
||||
A `Publisher` that emits an `ObjectSnapshot?` whenever changes occur in the `ObjectPublisher`. The event emits `nil` if the object has been deletd.
|
||||
|
||||
- SeeAlso: ObjectPublisher.reactive.snapshot(emitInitialValue:)
|
||||
*/
|
||||
public struct SnapshotPublisher: Publisher {
|
||||
|
||||
|
||||
@@ -25,16 +25,6 @@
|
||||
|
||||
import CoreData
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
#endif
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
import SwiftUI
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - ObjectPublisher
|
||||
|
||||
@@ -226,26 +216,10 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
fileprivate let rawObjectWillChange: Any?
|
||||
|
||||
fileprivate init(objectID: O.ObjectID, context: NSManagedObjectContext, initializer: @escaping (NSManagedObjectID, NSManagedObjectContext) -> ObjectSnapshot<O>?) {
|
||||
|
||||
self.id = objectID
|
||||
self.context = context
|
||||
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
|
||||
|
||||
#if canImport(Combine)
|
||||
self.rawObjectWillChange = ObservableObjectPublisher()
|
||||
|
||||
#else
|
||||
self.rawObjectWillChange = nil
|
||||
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
|
||||
self.rawObjectWillChange = nil
|
||||
}
|
||||
self.$lazySnapshot.initialize { [weak self] in
|
||||
|
||||
guard let self = self else {
|
||||
@@ -262,16 +236,12 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
|
||||
self.object = nil
|
||||
|
||||
self.willChange()
|
||||
self.$lazySnapshot.reset({ nil })
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
}
|
||||
else if updatedIDs.contains(objectID) {
|
||||
|
||||
self.willChange()
|
||||
self.$lazySnapshot.reset({ initializer(objectID, context) })
|
||||
self.didChange()
|
||||
self.notifyObservers()
|
||||
}
|
||||
}
|
||||
@@ -305,154 +275,6 @@ public final class ObjectPublisher<O: DynamicObject>: ObjectRepresentation, Hash
|
||||
}
|
||||
|
||||
|
||||
#if canImport(Combine)
|
||||
import Combine
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension ObjectPublisher: ObservableObject {}
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
extension ObjectPublisher: Publisher {
|
||||
|
||||
// MARK: Publisher
|
||||
|
||||
public typealias Output = ObjectSnapshot<O>
|
||||
public typealias Failure = Never
|
||||
|
||||
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
|
||||
|
||||
subscriber.receive(
|
||||
subscription: ObjectSnapshotSubscription(
|
||||
publisher: self,
|
||||
subscriber: subscriber
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectSnapshotSubscriber
|
||||
|
||||
fileprivate final class ObjectSnapshotSubscriber: Subscriber {
|
||||
|
||||
// MARK: Subscriber
|
||||
|
||||
typealias Failure = Never
|
||||
|
||||
func receive(subscription: Subscription) {
|
||||
|
||||
subscription.request(.unlimited)
|
||||
}
|
||||
|
||||
func receive(_ input: Output) -> Subscribers.Demand {
|
||||
|
||||
return .unlimited
|
||||
}
|
||||
|
||||
func receive(completion: Subscribers.Completion<Failure>) {}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectSnapshotSubscription
|
||||
|
||||
fileprivate final class ObjectSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
init(publisher: ObjectPublisher<O>, subscriber: S) {
|
||||
|
||||
self.publisher = publisher
|
||||
self.subscriber = subscriber
|
||||
}
|
||||
|
||||
|
||||
// MARK: Subscription
|
||||
|
||||
func request(_ demand: Subscribers.Demand) {
|
||||
|
||||
guard demand > 0 else {
|
||||
|
||||
return
|
||||
}
|
||||
self.publisher.addObserver(self) { [weak self] (publisher) in
|
||||
|
||||
guard let self = self, let subscriber = self.subscriber else {
|
||||
|
||||
return
|
||||
}
|
||||
if let snapshot = publisher.snapshot {
|
||||
|
||||
_ = subscriber.receive(snapshot)
|
||||
}
|
||||
else {
|
||||
|
||||
subscriber.receive(completion: .finished)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Cancellable
|
||||
|
||||
func cancel() {
|
||||
self.publisher.removeObserver(self)
|
||||
self.subscriber = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let publisher: ObjectPublisher<O>
|
||||
private var subscriber: S?
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: - ObjectPublisher
|
||||
|
||||
extension ObjectPublisher {
|
||||
|
||||
// MARK: ObservableObject
|
||||
|
||||
#if canImport(Combine)
|
||||
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
public var objectWillChange: ObservableObjectPublisher {
|
||||
|
||||
return self.rawObjectWillChange! as! ObservableObjectPublisher
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
fileprivate func willChange() {
|
||||
|
||||
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) else {
|
||||
|
||||
return
|
||||
}
|
||||
#if canImport(Combine)
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
|
||||
withAnimation {
|
||||
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
self.objectWillChange.send()
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
fileprivate func didChange() {
|
||||
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectPublisher where O: NSManagedObject
|
||||
|
||||
extension ObjectPublisher where O: NSManagedObject {
|
||||
|
||||
@@ -35,7 +35,7 @@ import SwiftUI
|
||||
A container view that reads changes to an `ObjectPublisher`
|
||||
*/
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
public struct ObjectReader<Object: DynamicObject, Content: View, Placeholder: View, Value>: View {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@@ -48,11 +48,29 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
public init(
|
||||
_ objectPublisher: ObjectPublisher<Object>?,
|
||||
@ViewBuilder content: @escaping (ObjectSnapshot<Object>) -> Content
|
||||
) where Value == ObjectSnapshot<Object>, Placeholder == EmptyView {
|
||||
|
||||
self._object = .init(objectPublisher)
|
||||
self.content = content
|
||||
self.placeholder = EmptyView.init
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an instance that creates views for `ObjectPublisher` changes.
|
||||
|
||||
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectReader` instance uses to create views dynamically
|
||||
- parameter content: The view builder that receives an `Optional<ObjectSnapshot<O>>` instance and creates views dynamically.
|
||||
- parameter placeholder: The view builder that creates a view for `nil` objects.
|
||||
*/
|
||||
public init(
|
||||
_ objectPublisher: ObjectPublisher<Object>?,
|
||||
@ViewBuilder content: @escaping (ObjectSnapshot<Object>) -> Content,
|
||||
@ViewBuilder placeholder: @escaping () -> Placeholder
|
||||
) where Value == ObjectSnapshot<Object> {
|
||||
|
||||
self._object = .init(objectPublisher)
|
||||
self.content = content
|
||||
self.keyPath = \.self
|
||||
self.placeholder = placeholder
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,11 +84,37 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
_ objectPublisher: ObjectPublisher<Object>?,
|
||||
keyPath: KeyPath<ObjectSnapshot<Object>, Value>,
|
||||
@ViewBuilder content: @escaping (Value) -> Content
|
||||
) {
|
||||
) where Placeholder == EmptyView {
|
||||
|
||||
self._object = .init(objectPublisher)
|
||||
self.content = content
|
||||
self.keyPath = keyPath
|
||||
self.content = {
|
||||
|
||||
content($0[keyPath: keyPath])
|
||||
}
|
||||
self.placeholder = EmptyView.init
|
||||
}
|
||||
|
||||
/**
|
||||
Creates an instance that creates views for `ObjectPublisher` changes.
|
||||
|
||||
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectReader` instance uses to create views dynamically
|
||||
- parameter keyPath: A `KeyPath` for a property in the `ObjectSnapshot` whose value will be sent to the views
|
||||
- parameter content: The view builder that receives the value from the property `KeyPath` and creates views dynamically.
|
||||
- parameter placeholder: The view builder that creates a view for `nil` objects.
|
||||
*/
|
||||
public init(
|
||||
_ objectPublisher: ObjectPublisher<Object>?,
|
||||
keyPath: KeyPath<ObjectSnapshot<Object>, Value>,
|
||||
@ViewBuilder content: @escaping (Value) -> Content,
|
||||
@ViewBuilder placeholder: @escaping () -> Placeholder
|
||||
) where Placeholder == EmptyView {
|
||||
|
||||
self._object = .init(objectPublisher)
|
||||
self.content = {
|
||||
|
||||
content($0[keyPath: keyPath])
|
||||
}
|
||||
self.placeholder = placeholder
|
||||
}
|
||||
|
||||
|
||||
@@ -80,18 +124,22 @@ public struct ObjectReader<Object: DynamicObject, Content: View, Value>: View {
|
||||
|
||||
if let object = self.object {
|
||||
|
||||
self.content(object[keyPath: self.keyPath])
|
||||
self.content(object)
|
||||
}
|
||||
else {
|
||||
|
||||
self.placeholder()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@LiveObject
|
||||
@ObjectState
|
||||
private var object: ObjectSnapshot<Object>?
|
||||
|
||||
private let content: (Value) -> Content
|
||||
private let keyPath: KeyPath<ObjectSnapshot<Object>, Value>
|
||||
private let content: (ObjectSnapshot<Object>) -> Content
|
||||
private let placeholder: () -> Placeholder
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -127,6 +127,11 @@ public struct ObjectSnapshot<O: DynamicObject>: ObjectRepresentation, Hashable {
|
||||
self.values = values
|
||||
self.generation = .init()
|
||||
}
|
||||
|
||||
internal var cs_objectID: O.ObjectID {
|
||||
|
||||
return self.objectID()
|
||||
}
|
||||
|
||||
|
||||
// MARK: FilePrivate
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// LiveObject.swift
|
||||
// ObjectState.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2021 John Rommel Estropia
|
||||
@@ -29,21 +29,21 @@ import Combine
|
||||
import SwiftUI
|
||||
|
||||
|
||||
// MARK: - LiveObject
|
||||
// MARK: - ObjectState
|
||||
|
||||
/**
|
||||
A property wrapper type that can read `ObjectPublisher` changes.
|
||||
*/
|
||||
@propertyWrapper
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
public struct LiveObject<O: DynamicObject>: DynamicProperty {
|
||||
public struct ObjectState<O: DynamicObject>: DynamicProperty {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Creates an instance that observes `ObjectPublisher` changes and exposes an `Optional<ObjectSnapshot<O>>` value.
|
||||
```
|
||||
@LiveObject
|
||||
@ObjectState
|
||||
var person: ObjectSnapshot<Person>?
|
||||
|
||||
init(objectPublisher: ObjectPublisher<Person>) {
|
||||
@@ -61,7 +61,7 @@ public struct LiveObject<O: DynamicObject>: DynamicProperty {
|
||||
}
|
||||
```
|
||||
|
||||
- parameter objectPublisher: The `ObjectPublisher` that the `LiveObject` will observe changes for
|
||||
- parameter objectPublisher: The `ObjectPublisher` that the `ObjectState` will observe changes for
|
||||
*/
|
||||
public init(_ objectPublisher: ObjectPublisher<O>?) {
|
||||
|
||||
@@ -76,6 +76,11 @@ public struct LiveObject<O: DynamicObject>: DynamicProperty {
|
||||
return self.observer.item
|
||||
}
|
||||
|
||||
public var projectedValue: ObjectPublisher<O>? {
|
||||
|
||||
return self.observer.objectPublisher
|
||||
}
|
||||
|
||||
|
||||
// MARK: DynamicProperty
|
||||
|
||||
@@ -205,15 +205,10 @@ public final class SQLiteStore: LocalStorage {
|
||||
[NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
```
|
||||
*/
|
||||
public let storeOptions: [AnyHashable: Any]? = autoreleasepool {
|
||||
|
||||
var storeOptions: [AnyHashable: Any] = [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
if #available(iOS 11.0, macOS 10.13, tvOSApplicationExtension 11.0, watchOSApplicationExtension 4.0, *) {
|
||||
|
||||
storeOptions[NSBinaryStoreInsecureDecodingCompatibilityOption] = true
|
||||
}
|
||||
return storeOptions
|
||||
}
|
||||
public let storeOptions: [AnyHashable: Any]? = [
|
||||
NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSBinaryStoreInsecureDecodingCompatibilityOption: true
|
||||
]
|
||||
|
||||
/**
|
||||
Do not call directly. Used by the `DataStack` internally.
|
||||
|
||||
@@ -39,7 +39,6 @@ import CoreData
|
||||
)
|
||||
```
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public struct SectionBy<O: DynamicObject> {
|
||||
|
||||
/**
|
||||
@@ -99,7 +98,6 @@ public struct SectionBy<O: DynamicObject> {
|
||||
|
||||
// MARK: - SectionBy where O: NSManagedObject
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension SectionBy where O: NSManagedObject {
|
||||
|
||||
/**
|
||||
@@ -152,7 +150,6 @@ extension SectionBy where O: NSManagedObject {
|
||||
|
||||
// MARK: - SectionBy where O: CoreStoreObject
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension SectionBy where O: CoreStoreObject {
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,6 @@ import CoreData
|
||||
)
|
||||
```
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public struct SectionMonitorChainBuilder<O: DynamicObject>: SectionMonitorBuilderType {
|
||||
|
||||
// MARK: SectionMonitorBuilderType
|
||||
@@ -62,7 +61,6 @@ public struct SectionMonitorChainBuilder<O: DynamicObject>: SectionMonitorBuilde
|
||||
/**
|
||||
Utility protocol for `SectionMonitorChainBuilder`. Used in methods that support chained fetch builders.
|
||||
*/
|
||||
@available(macOS 10.12, *)
|
||||
public protocol SectionMonitorBuilderType {
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,6 @@ import CoreData
|
||||
|
||||
// MARK: - UnsafeDataTransaction
|
||||
|
||||
@available(macOS 10.12, *)
|
||||
extension UnsafeDataTransaction {
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user