mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-07-15 17:22:45 +02:00
WIP
This commit is contained in:
@@ -19,6 +19,14 @@
|
|||||||
53EB03E72A3AD5D400179430 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EB03E52A3AD5D400179430 /* Operators.swift */; };
|
53EB03E72A3AD5D400179430 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EB03E52A3AD5D400179430 /* Operators.swift */; };
|
||||||
53EB03E82A3AD5D400179430 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EB03E52A3AD5D400179430 /* Operators.swift */; };
|
53EB03E82A3AD5D400179430 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EB03E52A3AD5D400179430 /* Operators.swift */; };
|
||||||
53EB03E92A3AD5D400179430 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EB03E52A3AD5D400179430 /* Operators.swift */; };
|
53EB03E92A3AD5D400179430 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EB03E52A3AD5D400179430 /* Operators.swift */; };
|
||||||
|
5F13955E2FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F13955D2FFCDBAC001EE0A4 /* DynamicObjectID.swift */; };
|
||||||
|
5F13955F2FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F13955D2FFCDBAC001EE0A4 /* DynamicObjectID.swift */; };
|
||||||
|
5F1395602FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F13955D2FFCDBAC001EE0A4 /* DynamicObjectID.swift */; };
|
||||||
|
5F1395612FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F13955D2FFCDBAC001EE0A4 /* DynamicObjectID.swift */; };
|
||||||
|
5F17507E2FFE35AF00BA577F /* Internals.Mutext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F17507D2FFE35AB00BA577F /* Internals.Mutext.swift */; };
|
||||||
|
5F17507F2FFE35AF00BA577F /* Internals.Mutext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F17507D2FFE35AB00BA577F /* Internals.Mutext.swift */; };
|
||||||
|
5F1750802FFE35AF00BA577F /* Internals.Mutext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F17507D2FFE35AB00BA577F /* Internals.Mutext.swift */; };
|
||||||
|
5F1750812FFE35AF00BA577F /* Internals.Mutext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F17507D2FFE35AB00BA577F /* Internals.Mutext.swift */; };
|
||||||
82BA18931C4BBCBA00A0916E /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82BA18891C4BBCBA00A0916E /* CoreStore.framework */; };
|
82BA18931C4BBCBA00A0916E /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82BA18891C4BBCBA00A0916E /* CoreStore.framework */; };
|
||||||
82BA18A01C4BBD1400A0916E /* CoreStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F03A53519C5C6DA005002A5 /* CoreStore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
82BA18A01C4BBD1400A0916E /* CoreStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F03A53519C5C6DA005002A5 /* CoreStore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
82BA18A21C4BBD1D00A0916E /* CoreStoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* CoreStoreError.swift */; };
|
82BA18A21C4BBD1D00A0916E /* CoreStoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* CoreStoreError.swift */; };
|
||||||
@@ -883,6 +891,8 @@
|
|||||||
2F03A53E19C5C6DA005002A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
2F03A53E19C5C6DA005002A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
|
2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
|
||||||
53EB03E52A3AD5D400179430 /* Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
|
53EB03E52A3AD5D400179430 /* Operators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = "<group>"; };
|
||||||
|
5F13955D2FFCDBAC001EE0A4 /* DynamicObjectID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicObjectID.swift; sourceTree = "<group>"; };
|
||||||
|
5F17507D2FFE35AB00BA577F /* Internals.Mutext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Internals.Mutext.swift; sourceTree = "<group>"; };
|
||||||
82BA18891C4BBCBA00A0916E /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
82BA18891C4BBCBA00A0916E /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
82BA18921C4BBCBA00A0916E /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
82BA18921C4BBCBA00A0916E /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
82BA18DE1C4BBE2600A0916E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.1.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
82BA18DE1C4BBE2600A0916E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.1.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
@@ -1453,6 +1463,7 @@
|
|||||||
B5831B6E1F3355C300A9F647 /* Legacy Properties */,
|
B5831B6E1F3355C300A9F647 /* Legacy Properties */,
|
||||||
B52F74391E9B8724005F3DAC /* Dynamic Schema */,
|
B52F74391E9B8724005F3DAC /* Dynamic Schema */,
|
||||||
B5D339DC1E9489C700C880DE /* DynamicObject.swift */,
|
B5D339DC1E9489C700C880DE /* DynamicObject.swift */,
|
||||||
|
5F13955D2FFCDBAC001EE0A4 /* DynamicObjectID.swift */,
|
||||||
B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */,
|
B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */,
|
||||||
B5D339E61E9493A500C880DE /* Entity.swift */,
|
B5D339E61E9493A500C880DE /* Entity.swift */,
|
||||||
B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */,
|
B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */,
|
||||||
@@ -1725,6 +1736,7 @@
|
|||||||
B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */,
|
B54A6A541BA15F2A007870FD /* Internals.FetchedResultsControllerDelegate.swift */,
|
||||||
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */,
|
B5BF7FCA234D80910070E741 /* Internals.LazyNonmutating.swift */,
|
||||||
B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */,
|
B5FAD6AB1B51285300714891 /* Internals.MigrationManager.swift */,
|
||||||
|
5F17507D2FFE35AB00BA577F /* Internals.Mutext.swift */,
|
||||||
B5E84F2B1AFF849C0064E85B /* Internals.NotificationObserver.swift */,
|
B5E84F2B1AFF849C0064E85B /* Internals.NotificationObserver.swift */,
|
||||||
B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */,
|
B5277676234F265F0056BE9F /* Internals.SharedNotificationObserver.swift */,
|
||||||
B50E17602351FA66004F033C /* Internals.Closure.swift */,
|
B50E17602351FA66004F033C /* Internals.Closure.swift */,
|
||||||
@@ -2131,6 +2143,7 @@
|
|||||||
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
B50E175223517C6B004F033C /* Internals.DiffableDataUIDispatcher.Changeset.swift in Sources */,
|
||||||
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
|
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
|
||||||
B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */,
|
B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */,
|
||||||
|
5F13955F2FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */,
|
||||||
B50C3EFE23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
B50C3EFE23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||||
B56E4EDF23CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
B56E4EDF23CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||||
B5C7959925D7D8B300BDACC1 /* ListReader.swift in Sources */,
|
B5C7959925D7D8B300BDACC1 /* ListReader.swift in Sources */,
|
||||||
@@ -2148,6 +2161,7 @@
|
|||||||
B5944EF625E269F9001D1D81 /* ObjectPublisher+Reactive.swift in Sources */,
|
B5944EF625E269F9001D1D81 /* ObjectPublisher+Reactive.swift in Sources */,
|
||||||
B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */,
|
B50392F91C478FF3009900CA /* NSManagedObject+Transaction.swift in Sources */,
|
||||||
B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */,
|
B5202CFA1C04688100DED140 /* NSFetchedResultsController+Convenience.swift in Sources */,
|
||||||
|
5F1750802FFE35AF00BA577F /* Internals.Mutext.swift in Sources */,
|
||||||
B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */,
|
B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */,
|
||||||
B5E84F2F1AFF849C0064E85B /* Internals.NotificationObserver.swift in Sources */,
|
B5E84F2F1AFF849C0064E85B /* Internals.NotificationObserver.swift in Sources */,
|
||||||
B5F1DA8D1B9AA97D007C5CBB /* ImportableObject.swift in Sources */,
|
B5F1DA8D1B9AA97D007C5CBB /* ImportableObject.swift in Sources */,
|
||||||
@@ -2348,6 +2362,7 @@
|
|||||||
B5831F432212700400D8604C /* Where.Expression.swift in Sources */,
|
B5831F432212700400D8604C /* Where.Expression.swift in Sources */,
|
||||||
B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
B51260941E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||||
B5FE4DA81C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
|
B5FE4DA81C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
|
||||||
|
5F1395602FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */,
|
||||||
B50C3EFF23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
B50C3EFF23D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||||
B56E4EE023CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
B56E4EE023CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||||
B5C7959A25D7D8B300BDACC1 /* ListReader.swift in Sources */,
|
B5C7959A25D7D8B300BDACC1 /* ListReader.swift in Sources */,
|
||||||
@@ -2365,6 +2380,7 @@
|
|||||||
82BA18D51C4BBD7100A0916E /* NSManagedObjectContext+Setup.swift in Sources */,
|
82BA18D51C4BBD7100A0916E /* NSManagedObjectContext+Setup.swift in Sources */,
|
||||||
B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
B5E41EC11EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
||||||
B5A1DAC91F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
B5A1DAC91F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||||
|
5F17507F2FFE35AF00BA577F /* Internals.Mutext.swift in Sources */,
|
||||||
B5FE4DA31C8481E100FA6A91 /* StorageInterface.swift in Sources */,
|
B5FE4DA31C8481E100FA6A91 /* StorageInterface.swift in Sources */,
|
||||||
B5C7959025D7D18000BDACC1 /* ListState.swift in Sources */,
|
B5C7959025D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||||
B56923C51EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
|
B56923C51EB823B4007C4DC9 /* NSEntityDescription+Migration.swift in Sources */,
|
||||||
@@ -2600,6 +2616,7 @@
|
|||||||
B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */,
|
B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */,
|
||||||
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */,
|
B5F8496F234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||||
B5831F452212700500D8604C /* Where.Expression.swift in Sources */,
|
B5831F452212700500D8604C /* Where.Expression.swift in Sources */,
|
||||||
|
5F13955E2FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */,
|
||||||
B5B866DE25E9012F00335476 /* ListPublisher+Reactive.swift in Sources */,
|
B5B866DE25E9012F00335476 /* ListPublisher+Reactive.swift in Sources */,
|
||||||
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
B514EF1423A8DB1E0093DBA4 /* DiffableDataSource.BaseAdapter.swift in Sources */,
|
||||||
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
B51260961E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||||
@@ -2617,6 +2634,7 @@
|
|||||||
B5220E131D1305ED009BC71E /* SectionBy.swift in Sources */,
|
B5220E131D1305ED009BC71E /* SectionBy.swift in Sources */,
|
||||||
B5C7959225D7D18000BDACC1 /* ListState.swift in Sources */,
|
B5C7959225D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||||
B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
B5E41EC31EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
||||||
|
5F1750812FFE35AF00BA577F /* Internals.Mutext.swift in Sources */,
|
||||||
B5A1DACB1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
B5A1DACB1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||||
B52DD1B81BE1F94000949AFE /* DataStack+Migration.swift in Sources */,
|
B52DD1B81BE1F94000949AFE /* DataStack+Migration.swift in Sources */,
|
||||||
B5D8CA792346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */,
|
B5D8CA792346EAEF0055D7D1 /* DataStack+DataSources.swift in Sources */,
|
||||||
@@ -2817,6 +2835,7 @@
|
|||||||
B5831F442212700500D8604C /* Where.Expression.swift in Sources */,
|
B5831F442212700500D8604C /* Where.Expression.swift in Sources */,
|
||||||
B50C3F0023D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
B50C3F0023D1AB1400B29880 /* FieldCoders.Plist.swift in Sources */,
|
||||||
B56E4EE123CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
B56E4EE123CEBCF000E1708C /* FieldOptionalType.swift in Sources */,
|
||||||
|
5F1395612FFCDBEF001EE0A4 /* DynamicObjectID.swift in Sources */,
|
||||||
B5C7959B25D7D8B300BDACC1 /* ListReader.swift in Sources */,
|
B5C7959B25D7D8B300BDACC1 /* ListReader.swift in Sources */,
|
||||||
B5F8496E234898240029D57B /* ListSnapshot.swift in Sources */,
|
B5F8496E234898240029D57B /* ListSnapshot.swift in Sources */,
|
||||||
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
B51260951E9B28F100402229 /* Internals.EntityIdentifier.swift in Sources */,
|
||||||
@@ -2834,6 +2853,7 @@
|
|||||||
B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */,
|
B56321B31BD6521C006C9394 /* NSManagedObjectContext+Setup.swift in Sources */,
|
||||||
B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
B5E41EC21EA9BB37006240F0 /* DynamicSchema+Convenience.swift in Sources */,
|
||||||
B5C7959125D7D18000BDACC1 /* ListState.swift in Sources */,
|
B5C7959125D7D18000BDACC1 /* ListState.swift in Sources */,
|
||||||
|
5F17507E2FFE35AF00BA577F /* Internals.Mutext.swift in Sources */,
|
||||||
B5A1DACA1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
B5A1DACA1F111BFA003CF369 /* KeyPath+Querying.swift in Sources */,
|
||||||
B56321AE1BD6521C006C9394 /* Internals.NotificationObserver.swift in Sources */,
|
B56321AE1BD6521C006C9394 /* Internals.NotificationObserver.swift in Sources */,
|
||||||
B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */,
|
B56321931BD65216006C9394 /* DataStack+Querying.swift in Sources */,
|
||||||
@@ -3027,7 +3047,7 @@
|
|||||||
SWIFT_COMPILATION_MODE = singlefile;
|
SWIFT_COMPILATION_MODE = singlefile;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
VERSIONING_SYSTEM = "apple-generic";
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
@@ -3092,7 +3112,7 @@
|
|||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||||
SWIFT_VERSION = 6.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TVOS_DEPLOYMENT_TARGET = 17.0;
|
TVOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
|
|||||||
@@ -24,11 +24,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
import os
|
|
||||||
|
|
||||||
@testable
|
@testable
|
||||||
import CoreStore
|
import CoreStore
|
||||||
|
|
||||||
|
|
||||||
#if !SWIFT_PACKAGE
|
#if !SWIFT_PACKAGE
|
||||||
|
|
||||||
extension Bundle {
|
extension Bundle {
|
||||||
@@ -196,7 +196,7 @@ final class TestLogger: CoreStoreLogger {
|
|||||||
|
|
||||||
init(_ expectations: [Expectation: XCTestExpectation]) {
|
init(_ expectations: [Expectation: XCTestExpectation]) {
|
||||||
|
|
||||||
self.expectations = .init(initialState: expectations)
|
self.expectations = .init(expectations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ final class TestLogger: CoreStoreLogger {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let expectations: OSAllocatedUnfairLock<[Expectation: XCTestExpectation]>
|
private let expectations: Internals.Mutex<[Expectation: XCTestExpectation]>
|
||||||
|
|
||||||
private func fulfill(_ expectation: Expectation) {
|
private func fulfill(_ expectation: Expectation) {
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ class Person: CoreStoreObject {
|
|||||||
|
|
||||||
class DynamicModelTests: BaseTestDataTestCase {
|
class DynamicModelTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDynamicModels_CanBeDeclaredCorrectly() {
|
dynamic func test_ThatDynamicModels_CanBeDeclaredCorrectly() {
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import CoreStore
|
|||||||
|
|
||||||
final class FetchTests: BaseTestDataTestCase {
|
final class FetchTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacksAndTransactions_CanFetchOneExisting() {
|
dynamic func test_ThatDataStacksAndTransactions_CanFetchOneExisting() {
|
||||||
|
|
||||||
@@ -103,14 +104,14 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
|
||||||
|
let persistentID = object.persistentID()
|
||||||
let fetchExpectation = self.expectation(description: "fetch")
|
let fetchExpectation = self.expectation(description: "fetch")
|
||||||
var existing1: TestEntity1?
|
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
existing1 = transaction.fetchExisting(object)
|
let existing1 = transaction.fetchExisting(persistentID)
|
||||||
XCTAssertNotNil(existing1)
|
XCTAssertNotNil(existing1)
|
||||||
XCTAssertEqual(existing1!.objectID, object.objectID)
|
XCTAssertEqual(existing1!.persistentID(), persistentID)
|
||||||
XCTAssertEqual(existing1!.managedObjectContext, transaction.context)
|
XCTAssertEqual(existing1!.managedObjectContext, transaction.context)
|
||||||
|
|
||||||
try transaction.cancel()
|
try transaction.cancel()
|
||||||
@@ -123,9 +124,9 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(error, CoreStoreError.userCancelled)
|
XCTAssertEqual(error, CoreStoreError.userCancelled)
|
||||||
|
|
||||||
let existing2 = stack.fetchExisting(existing1!)
|
let existing2 = stack.fetchExisting(persistentID)
|
||||||
XCTAssertNotNil(existing2)
|
XCTAssertNotNil(existing2)
|
||||||
XCTAssertEqual(existing2!.objectID, object.objectID)
|
XCTAssertEqual(existing2!.persistentID(), persistentID)
|
||||||
XCTAssertEqual(existing2!.managedObjectContext, stack.mainContext)
|
XCTAssertEqual(existing2!.managedObjectContext, stack.mainContext)
|
||||||
|
|
||||||
fetchExpectation.fulfill()
|
fetchExpectation.fulfill()
|
||||||
@@ -136,6 +137,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacksAndTransactions_CanFetchAllExisting() {
|
dynamic func test_ThatDataStacksAndTransactions_CanFetchAllExisting() {
|
||||||
|
|
||||||
@@ -230,15 +232,15 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
|
||||||
|
let persistentIDs = objects.map({ $0.persistentID() })
|
||||||
let fetchExpectation = self.expectation(description: "fetch")
|
let fetchExpectation = self.expectation(description: "fetch")
|
||||||
var existing1 = [TestEntity1]()
|
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { (transaction) in
|
||||||
|
|
||||||
existing1 = transaction.fetchExisting(objects)
|
let existing1 = transaction.fetchExisting(persistentIDs)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
existing1.map { $0.objectID },
|
existing1.map { $0.persistentID() },
|
||||||
objects.map { $0.objectID }
|
persistentIDs
|
||||||
)
|
)
|
||||||
for object in existing1 {
|
for object in existing1 {
|
||||||
|
|
||||||
@@ -254,10 +256,10 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(error, CoreStoreError.userCancelled)
|
XCTAssertEqual(error, CoreStoreError.userCancelled)
|
||||||
|
|
||||||
let existing2 = stack.fetchExisting(existing1)
|
let existing2 = stack.fetchExisting(persistentIDs)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
existing2.map { $0.objectID },
|
existing2.map { $0.persistentID() },
|
||||||
objects.map { $0.objectID }
|
persistentIDs
|
||||||
)
|
)
|
||||||
for object in existing2 {
|
for object in existing2 {
|
||||||
|
|
||||||
@@ -271,6 +273,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchOneFromDefaultConfiguration() {
|
dynamic func test_ThatDataStacks_CanFetchOneFromDefaultConfiguration() {
|
||||||
|
|
||||||
@@ -404,6 +407,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchOneFromSingleConfiguration() {
|
dynamic func test_ThatDataStacks_CanFetchOneFromSingleConfiguration() {
|
||||||
|
|
||||||
@@ -581,6 +585,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchOneFromMultipleConfigurations() {
|
dynamic func test_ThatDataStacks_CanFetchOneFromMultipleConfigurations() {
|
||||||
|
|
||||||
@@ -724,6 +729,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchAllFromDefaultConfiguration() {
|
dynamic func test_ThatDataStacks_CanFetchAllFromDefaultConfiguration() {
|
||||||
|
|
||||||
@@ -912,6 +918,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchAllFromSingleConfiguration() {
|
dynamic func test_ThatDataStacks_CanFetchAllFromSingleConfiguration() {
|
||||||
|
|
||||||
@@ -1125,6 +1132,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchAllFromMultipleConfigurations() {
|
dynamic func test_ThatDataStacks_CanFetchAllFromMultipleConfigurations() {
|
||||||
|
|
||||||
@@ -1306,6 +1314,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchCountFromDefaultConfiguration() {
|
dynamic func test_ThatDataStacks_CanFetchCountFromDefaultConfiguration() {
|
||||||
|
|
||||||
@@ -1420,6 +1429,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchCountFromSingleConfiguration() {
|
dynamic func test_ThatDataStacks_CanFetchCountFromSingleConfiguration() {
|
||||||
|
|
||||||
@@ -1567,6 +1577,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatDataStacks_CanFetchCountFromMultipleConfigurations() {
|
dynamic func test_ThatDataStacks_CanFetchCountFromMultipleConfigurations() {
|
||||||
|
|
||||||
@@ -1677,6 +1688,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchOneFromDefaultConfiguration() {
|
dynamic func test_ThatTransactions_CanFetchOneFromDefaultConfiguration() {
|
||||||
|
|
||||||
@@ -1818,6 +1830,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchOneFromSingleConfiguration() {
|
dynamic func test_ThatTransactions_CanFetchOneFromSingleConfiguration() {
|
||||||
|
|
||||||
@@ -2006,6 +2019,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchOneFromMultipleConfigurations() {
|
dynamic func test_ThatTransactions_CanFetchOneFromMultipleConfigurations() {
|
||||||
|
|
||||||
@@ -2157,6 +2171,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchAllFromDefaultConfiguration() {
|
dynamic func test_ThatTransactions_CanFetchAllFromDefaultConfiguration() {
|
||||||
|
|
||||||
@@ -2353,6 +2368,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchAllFromSingleConfiguration() {
|
dynamic func test_ThatTransactions_CanFetchAllFromSingleConfiguration() {
|
||||||
|
|
||||||
@@ -2589,6 +2605,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchAllFromMultipleConfigurations() {
|
dynamic func test_ThatTransactions_CanFetchAllFromMultipleConfigurations() {
|
||||||
|
|
||||||
@@ -2790,6 +2807,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchCountFromDefaultConfiguration() {
|
dynamic func test_ThatTransactions_CanFetchCountFromDefaultConfiguration() {
|
||||||
|
|
||||||
@@ -2918,6 +2936,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchCountFromSingleConfiguration() {
|
dynamic func test_ThatTransactions_CanFetchCountFromSingleConfiguration() {
|
||||||
|
|
||||||
@@ -3076,6 +3095,7 @@ final class FetchTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTransactions_CanFetchCountFromMultipleConfigurations() {
|
dynamic func test_ThatTransactions_CanFetchCountFromMultipleConfigurations() {
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ final class FromTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultConfigurations() {
|
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultConfigurations() {
|
||||||
|
|
||||||
self.prepareStack { (dataStack) in
|
self.prepareStack { (dataStack) in
|
||||||
@@ -106,6 +107,7 @@ final class FromTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForSingleConfigurations() {
|
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForSingleConfigurations() {
|
||||||
|
|
||||||
self.prepareStack(configurations: ["Config1"]) { (dataStack) in
|
self.prepareStack(configurations: ["Config1"]) { (dataStack) in
|
||||||
@@ -212,6 +214,7 @@ final class FromTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultAndCustomConfigurations() {
|
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultAndCustomConfigurations() {
|
||||||
|
|
||||||
self.prepareStack(configurations: [nil, "Config1"]) { (dataStack) in
|
self.prepareStack(configurations: [nil, "Config1"]) { (dataStack) in
|
||||||
@@ -316,6 +319,7 @@ final class FromTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForMultipleConfigurations() {
|
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForMultipleConfigurations() {
|
||||||
|
|
||||||
self.prepareStack(configurations: ["Config1", "Config2"]) { (dataStack) in
|
self.prepareStack(configurations: ["Config1", "Config2"]) { (dataStack) in
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(Double(MAXFLOAT).cs_toQueryableNativeType(), NSNumber(value: Double(MAXFLOAT)))
|
XCTAssertEqual(Double(MAXFLOAT).cs_toQueryableNativeType(), NSNumber(value: Double(MAXFLOAT)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportObject_CanSkipImport() {
|
dynamic func test_ThatImportObject_CanSkipImport() {
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportObject_CanThrowError() {
|
dynamic func test_ThatImportObject_CanThrowError() {
|
||||||
|
|
||||||
@@ -160,6 +162,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportObject_CanImportCorrectly() {
|
dynamic func test_ThatImportObject_CanImportCorrectly() {
|
||||||
|
|
||||||
@@ -220,6 +223,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportObjects_CanSkipImport() {
|
dynamic func test_ThatImportObjects_CanSkipImport() {
|
||||||
|
|
||||||
@@ -275,6 +279,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportObjects_CanThrowError() {
|
dynamic func test_ThatImportObjects_CanThrowError() {
|
||||||
|
|
||||||
@@ -339,6 +344,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportObjects_CanImportCorrectly() {
|
dynamic func test_ThatImportObjects_CanImportCorrectly() {
|
||||||
|
|
||||||
@@ -397,6 +403,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObject_CanSkipImport() {
|
dynamic func test_ThatImportUniqueObject_CanSkipImport() {
|
||||||
|
|
||||||
@@ -466,6 +473,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObjects_ImportsLastOfImportSourcesWithSameIDs() {
|
dynamic func test_ThatImportUniqueObjects_ImportsLastOfImportSourcesWithSameIDs() {
|
||||||
|
|
||||||
@@ -524,6 +532,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObjects_MaintainsOrderOfInputSourceArray() {
|
dynamic func test_ThatImportUniqueObjects_MaintainsOrderOfInputSourceArray() {
|
||||||
|
|
||||||
@@ -585,6 +594,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObject_CanThrowError() {
|
dynamic func test_ThatImportUniqueObject_CanThrowError() {
|
||||||
|
|
||||||
@@ -681,6 +691,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObject_CanImportCorrectly() {
|
dynamic func test_ThatImportUniqueObject_CanImportCorrectly() {
|
||||||
|
|
||||||
@@ -758,6 +769,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObjects_CanSkipImport() {
|
dynamic func test_ThatImportUniqueObjects_CanSkipImport() {
|
||||||
|
|
||||||
@@ -816,6 +828,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObjects_CanThrowError() {
|
dynamic func test_ThatImportUniqueObjects_CanThrowError() {
|
||||||
|
|
||||||
@@ -977,6 +990,7 @@ class ImportTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatImportUniqueObjects_CanImportCorrectly() {
|
dynamic func test_ThatImportUniqueObjects_CanImportCorrectly() {
|
||||||
|
|
||||||
@@ -1065,7 +1079,7 @@ extension TestEntity1: ImportableUniqueObject {
|
|||||||
|
|
||||||
// MARK: ImportableObject
|
// MARK: ImportableObject
|
||||||
|
|
||||||
typealias ImportSource = [String: Any]
|
typealias ImportSource = [String: any Sendable]
|
||||||
|
|
||||||
static func shouldInsert(from source: ImportSource, in transaction: BaseDataTransaction) -> Bool {
|
static func shouldInsert(from source: ImportSource, in transaction: BaseDataTransaction) -> Bool {
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import CoreStore
|
|||||||
|
|
||||||
class ListObserverTests: BaseTestDataTestCase {
|
class ListObserverTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatListObservers_CanReceiveInsertNotifications() {
|
dynamic func test_ThatListObservers_CanReceiveInsertNotifications() {
|
||||||
|
|
||||||
@@ -51,13 +52,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertFalse(monitor.hasObjects())
|
XCTAssertFalse(monitor.hasObjects())
|
||||||
XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
|
XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
|
||||||
|
|
||||||
var events = 0
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
defer {
|
defer {
|
||||||
@@ -66,12 +69,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 1)
|
XCTAssertEqual(events, 1)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
((note.userInfo as NSDictionary?) ?? [:]),
|
||||||
@@ -86,11 +92,14 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 1
|
return events == 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { [dateFormatter] (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 2)
|
XCTAssertEqual(events, 2)
|
||||||
|
|
||||||
@@ -111,13 +120,14 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
||||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
||||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||||
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
|
XCTAssertEqual(object?.testDate, dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
events += 1
|
events += 1
|
||||||
}
|
}
|
||||||
return events == 2
|
return events == 2
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||||
@@ -125,16 +135,19 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
events += 1
|
events += 1
|
||||||
}
|
}
|
||||||
return events == 3
|
return events == 3
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { [dateFormatter] (transaction) -> Bool in
|
||||||
|
|
||||||
let object = transaction.create(Into<TestEntity1>())
|
let object = transaction.create(Into<TestEntity1>())
|
||||||
object.testBoolean = NSNumber(value: true)
|
object.testBoolean = NSNumber(value: true)
|
||||||
@@ -142,7 +155,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
object.testDecimal = NSDecimalNumber(string: "1")
|
object.testDecimal = NSDecimalNumber(string: "1")
|
||||||
object.testString = "nil:TestEntity1:1"
|
object.testString = "nil:TestEntity1:1"
|
||||||
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||||
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
|
object.testDate = dateFormatter.date(from: "2000-01-01T00:00:00Z")!
|
||||||
|
|
||||||
return transaction.hasChanges
|
return transaction.hasChanges
|
||||||
},
|
},
|
||||||
@@ -160,6 +173,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
|
dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
|
||||||
|
|
||||||
@@ -182,13 +196,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(monitor.numberOfObjects(in: 0), 2)
|
XCTAssertEqual(monitor.numberOfObjects(in: 0), 2)
|
||||||
XCTAssertEqual(monitor.numberOfObjects(in: 1), 3)
|
XCTAssertEqual(monitor.numberOfObjects(in: 1), 3)
|
||||||
|
|
||||||
var events = 0
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
defer {
|
defer {
|
||||||
@@ -197,12 +213,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { [dateFormatter] (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssert(events == 1 || events == 2)
|
XCTAssert(events == 1 || events == 2)
|
||||||
|
|
||||||
@@ -227,7 +246,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
|
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
|
||||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
|
XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
|
||||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||||
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
|
XCTAssertEqual(object?.testDate, dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
|
||||||
|
|
||||||
case NSNumber(value: 102)?:
|
case NSNumber(value: 102)?:
|
||||||
XCTAssertEqual(indexPath?.section, 0)
|
XCTAssertEqual(indexPath?.section, 0)
|
||||||
@@ -238,7 +257,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
|
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
|
||||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
|
XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
|
||||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||||
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
|
XCTAssertEqual(object?.testDate, dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
XCTFail()
|
XCTFail()
|
||||||
@@ -249,24 +268,28 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 1 || events == 2
|
return events == 1 || events == 2
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
XCTAssertEqual(events, 3)
|
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
|
XCTAssertEqual(events, 3)
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
events += 1
|
events += 1
|
||||||
}
|
}
|
||||||
return events == 3
|
return events == 3
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { [dateFormatter] (transaction) -> Bool in
|
||||||
|
|
||||||
if let object = try transaction.fetchOne(
|
if let object = try transaction.fetchOne(
|
||||||
From<TestEntity1>(),
|
From<TestEntity1>(),
|
||||||
@@ -276,7 +299,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
object.testDecimal = NSDecimalNumber(string: "11")
|
object.testDecimal = NSDecimalNumber(string: "11")
|
||||||
object.testString = "nil:TestEntity1:11"
|
object.testString = "nil:TestEntity1:11"
|
||||||
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||||
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
object.testDate = dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
@@ -290,7 +313,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
object.testDecimal = NSDecimalNumber(string: "22")
|
object.testDecimal = NSDecimalNumber(string: "22")
|
||||||
object.testString = "nil:TestEntity1:22"
|
object.testString = "nil:TestEntity1:22"
|
||||||
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||||
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
object.testDate = dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
@@ -312,6 +335,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
|
dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
|
||||||
|
|
||||||
@@ -327,13 +351,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
)
|
)
|
||||||
monitor.addObserver(observer)
|
monitor.addObserver(observer)
|
||||||
|
|
||||||
var events = 0
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
defer {
|
defer {
|
||||||
@@ -342,12 +368,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 1)
|
XCTAssertEqual(events, 1)
|
||||||
|
|
||||||
let userInfo = note.userInfo
|
let userInfo = note.userInfo
|
||||||
@@ -375,12 +404,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 1
|
return events == 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 2)
|
XCTAssertEqual(events, 2)
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
defer {
|
defer {
|
||||||
@@ -389,6 +421,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 2
|
return events == 2
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
@@ -420,6 +453,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
|
dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
|
||||||
|
|
||||||
@@ -435,13 +469,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
)
|
)
|
||||||
monitor.addObserver(observer)
|
monitor.addObserver(observer)
|
||||||
|
|
||||||
var events = 0
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
defer {
|
defer {
|
||||||
@@ -450,12 +486,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssert(events == 1 || events == 2)
|
XCTAssert(events == 1 || events == 2)
|
||||||
|
|
||||||
let userInfo = note.userInfo
|
let userInfo = note.userInfo
|
||||||
@@ -479,12 +518,15 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 1 || events == 2
|
return events == 1 || events == 2
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 3)
|
XCTAssertEqual(events, 3)
|
||||||
|
|
||||||
let userInfo = note.userInfo
|
let userInfo = note.userInfo
|
||||||
@@ -507,20 +549,24 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
return events == 3
|
return events == 3
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
XCTAssertEqual(events, 4)
|
|
||||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
|
XCTAssertEqual(events, 4)
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
events += 1
|
events += 1
|
||||||
}
|
}
|
||||||
return events == 4
|
return events == 4
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
stack.perform(
|
stack.perform(
|
||||||
@@ -551,7 +597,7 @@ class ListObserverTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
// MARK: TestListObserver
|
// MARK: TestListObserver
|
||||||
|
|
||||||
class TestListObserver: ListSectionObserver {
|
final class TestListObserver: ListSectionObserver {
|
||||||
|
|
||||||
// MARK: ListObserver
|
// MARK: ListObserver
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
import os
|
|
||||||
|
|
||||||
@testable
|
@testable
|
||||||
import CoreStore
|
import CoreStore
|
||||||
@@ -56,7 +55,8 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(monitor.object, object)
|
XCTAssertEqual(monitor.object, object)
|
||||||
XCTAssertFalse(monitor.isObjectDeleted)
|
XCTAssertFalse(monitor.isObjectDeleted)
|
||||||
|
|
||||||
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
|
let persistentID = object.persistentID()
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
|
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
|
||||||
@@ -68,8 +68,8 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
(note.userInfo?["object"] as? TestEntity1)?.persistentID(),
|
||||||
["object": object] as NSDictionary
|
persistentID
|
||||||
)
|
)
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
@@ -89,16 +89,17 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(events, 1)
|
XCTAssertEqual(events, 1)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
(note.userInfo?["object"] as? TestEntity1)?.persistentID(),
|
||||||
[
|
persistentID
|
||||||
"object": object,
|
)
|
||||||
"changedPersistentKeys": Set(
|
XCTAssertEqual(
|
||||||
|
note.userInfo?["changedPersistentKeys"] as? Set<String>,
|
||||||
|
Set(
|
||||||
[
|
[
|
||||||
#keyPath(TestEntity1.testNumber),
|
#keyPath(TestEntity1.testNumber),
|
||||||
#keyPath(TestEntity1.testString)
|
#keyPath(TestEntity1.testString)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
] as NSDictionary
|
|
||||||
)
|
)
|
||||||
let object = note.userInfo?["object"] as? TestEntity1
|
let object = note.userInfo?["object"] as? TestEntity1
|
||||||
XCTAssertEqual(object?.testNumber, NSNumber(value: 10))
|
XCTAssertEqual(object?.testNumber, NSNumber(value: 10))
|
||||||
@@ -116,7 +117,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { (transaction) -> Bool in
|
||||||
|
|
||||||
guard let object = transaction.edit(object) else {
|
guard let object = transaction.edit(persistentID) else {
|
||||||
|
|
||||||
XCTFail()
|
XCTFail()
|
||||||
try transaction.cancel()
|
try transaction.cancel()
|
||||||
@@ -162,7 +163,8 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
XCTAssertEqual(monitor.object, object)
|
XCTAssertEqual(monitor.object, object)
|
||||||
XCTAssertFalse(monitor.isObjectDeleted)
|
XCTAssertFalse(monitor.isObjectDeleted)
|
||||||
|
|
||||||
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
|
let persistentID = object.persistentID()
|
||||||
|
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
|
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
|
||||||
@@ -174,8 +176,8 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertEqual(
|
XCTAssertEqual(
|
||||||
((note.userInfo as NSDictionary?) ?? [:]),
|
(note.userInfo?["object"] as? TestEntity1)?.persistentID(),
|
||||||
["object": object] as NSDictionary
|
persistentID
|
||||||
)
|
)
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
@@ -189,7 +191,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
|
|||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { (transaction) -> Bool in
|
||||||
|
|
||||||
guard let object = transaction.edit(object) else {
|
guard let object = transaction.edit(persistentID) else {
|
||||||
|
|
||||||
XCTFail()
|
XCTFail()
|
||||||
try transaction.cancel()
|
try transaction.cancel()
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ class ObjectPublisherTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
|
let persistentID = object.persistentID()
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { (transaction) -> Bool in
|
||||||
|
|
||||||
guard let object = transaction.edit(object) else {
|
guard let object = transaction.edit(persistentID) else {
|
||||||
|
|
||||||
XCTFail()
|
XCTFail()
|
||||||
try transaction.cancel()
|
try transaction.cancel()
|
||||||
@@ -123,10 +124,11 @@ class ObjectPublisherTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
|
let persistentID = object.persistentID()
|
||||||
stack.perform(
|
stack.perform(
|
||||||
asynchronous: { (transaction) -> Bool in
|
asynchronous: { (transaction) -> Bool in
|
||||||
|
|
||||||
guard let object = transaction.edit(object) else {
|
guard let object = transaction.edit(persistentID) else {
|
||||||
|
|
||||||
XCTFail()
|
XCTFail()
|
||||||
try transaction.cancel()
|
try transaction.cancel()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import CoreStore
|
|||||||
class QueryTests: BaseTestDataTestCase {
|
class QueryTests: BaseTestDataTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryAttributeValue() {
|
dynamic func test_ThatDataStacks_CanQueryAttributeValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -219,6 +220,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryAverageValue() {
|
dynamic func test_ThatDataStacks_CanQueryAverageValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -398,6 +400,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryCountValue() {
|
dynamic func test_ThatDataStacks_CanQueryCountValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -575,6 +578,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryMaximumValue() {
|
dynamic func test_ThatDataStacks_CanQueryMaximumValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -759,6 +763,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryMinimumValue() {
|
dynamic func test_ThatDataStacks_CanQueryMinimumValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -943,6 +948,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQuerySumValue() {
|
dynamic func test_ThatDataStacks_CanQuerySumValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -1121,6 +1127,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryObjectIDValue() {
|
dynamic func test_ThatDataStacks_CanQueryObjectIDValue() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -1289,6 +1296,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryAttributes() {
|
dynamic func test_ThatDataStacks_CanQueryAttributes() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
@@ -1343,6 +1351,7 @@ class QueryTests: BaseTestDataTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatDataStacks_CanQueryAggregates() {
|
dynamic func test_ThatDataStacks_CanQueryAggregates() {
|
||||||
|
|
||||||
let configurations: [ModelConfiguration] = [nil]
|
let configurations: [ModelConfiguration] = [nil]
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import CoreData
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
@testable
|
@testable
|
||||||
|
@_spi(Internals)
|
||||||
import CoreStore
|
import CoreStore
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import CoreStore
|
|||||||
final class TransactionTests: BaseTestCase {
|
final class TransactionTests: BaseTestCase {
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatSynchronousTransactions_CanPerformCRUDs() {
|
dynamic func test_ThatSynchronousTransactions_CanPerformCRUDs() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -151,6 +152,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatSynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
dynamic func test_ThatSynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
||||||
|
|
||||||
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
||||||
@@ -270,6 +272,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatSynchronousTransactions_CanDiscardUncommittedChanges() {
|
dynamic func test_ThatSynchronousTransactions_CanDiscardUncommittedChanges() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -385,6 +388,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatSynchronousTransactions_CanCommitWithoutWaitingForMerges() {
|
dynamic func test_ThatSynchronousTransactions_CanCommitWithoutWaitingForMerges() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -398,12 +402,14 @@ final class TransactionTests: BaseTestCase {
|
|||||||
|
|
||||||
XCTAssertFalse(monitor.hasObjects())
|
XCTAssertFalse(monitor.hasObjects())
|
||||||
|
|
||||||
var events = 0
|
let events: Internals.Mutex<Int> = .init(0)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events, 0)
|
||||||
XCTAssertTrue(note.userInfo?.isEmpty != false)
|
XCTAssertTrue(note.userInfo?.isEmpty != false)
|
||||||
defer {
|
defer {
|
||||||
@@ -412,12 +418,15 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
return events == 0
|
return events == 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||||
object: observer,
|
object: observer,
|
||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
XCTAssertEqual(events, 1)
|
XCTAssertEqual(events, 1)
|
||||||
|
|
||||||
let userInfo = note.userInfo
|
let userInfo = note.userInfo
|
||||||
@@ -442,6 +451,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
return events == 1
|
return events == 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
_ = self.expectation(
|
_ = self.expectation(
|
||||||
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
|
||||||
@@ -449,12 +459,15 @@ final class TransactionTests: BaseTestCase {
|
|||||||
handler: { (note) -> Bool in
|
handler: { (note) -> Bool in
|
||||||
|
|
||||||
XCTAssertTrue(note.userInfo?.isEmpty != false)
|
XCTAssertTrue(note.userInfo?.isEmpty != false)
|
||||||
|
return events.withLock { events in
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
events += 1
|
events += 1
|
||||||
}
|
}
|
||||||
return events == 2
|
return events == 2
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
let saveExpectation = self.expectation(description: "save")
|
let saveExpectation = self.expectation(description: "save")
|
||||||
do {
|
do {
|
||||||
@@ -479,7 +492,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
|
|
||||||
XCTFail()
|
XCTFail()
|
||||||
}
|
}
|
||||||
XCTAssertEqual(events, 0)
|
XCTAssertEqual(events.withLock({ $0 }), 0)
|
||||||
XCTAssertEqual(monitor.numberOfObjects(), 0)
|
XCTAssertEqual(monitor.numberOfObjects(), 0)
|
||||||
self.waitAndCheckExpectations()
|
self.waitAndCheckExpectations()
|
||||||
}
|
}
|
||||||
@@ -487,6 +500,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
|
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDs() {
|
dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDs() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -626,6 +640,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
||||||
|
|
||||||
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
||||||
@@ -759,6 +774,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatAsynchronousTransactions_CanDiscardUncommittedChanges() {
|
dynamic func test_ThatAsynchronousTransactions_CanDiscardUncommittedChanges() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -900,6 +916,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatUnsafeTransactions_CanPerformCRUDs() {
|
dynamic func test_ThatUnsafeTransactions_CanPerformCRUDs() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
@@ -995,6 +1012,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatUnsafeTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
dynamic func test_ThatUnsafeTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
||||||
|
|
||||||
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
||||||
@@ -1083,6 +1101,7 @@ final class TransactionTests: BaseTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
|
@MainActor
|
||||||
dynamic func test_ThatUnsafeTransactions_CanRollbackChanges() {
|
dynamic func test_ThatUnsafeTransactions_CanRollbackChanges() {
|
||||||
|
|
||||||
self.prepareStack { (stack) in
|
self.prepareStack { (stack) in
|
||||||
|
|||||||
@@ -37,18 +37,17 @@ final class TweakTests: XCTestCase {
|
|||||||
@objc
|
@objc
|
||||||
dynamic func test_ThatTweakClauses_ApplyToFetchRequestsCorrectly() {
|
dynamic func test_ThatTweakClauses_ApplyToFetchRequestsCorrectly() {
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "%K == %@", "key", "value")
|
|
||||||
let tweak = Tweak {
|
let tweak = Tweak {
|
||||||
|
|
||||||
$0.fetchOffset = 100
|
$0.fetchOffset = 100
|
||||||
$0.fetchLimit = 200
|
$0.fetchLimit = 200
|
||||||
$0.predicate = predicate
|
$0.predicate = NSPredicate(format: "%K == %@", "key", "value")
|
||||||
}
|
}
|
||||||
let request = Internals.CoreStoreFetchRequest<NSFetchRequestResult>()
|
let request = Internals.CoreStoreFetchRequest<NSFetchRequestResult>()
|
||||||
tweak.applyToFetchRequest(request)
|
tweak.applyToFetchRequest(request)
|
||||||
XCTAssertEqual(request.fetchOffset, 100)
|
XCTAssertEqual(request.fetchOffset, 100)
|
||||||
XCTAssertEqual(request.fetchLimit, 200)
|
XCTAssertEqual(request.fetchLimit, 200)
|
||||||
XCTAssertNotNil(request.predicate)
|
XCTAssertNotNil(request.predicate)
|
||||||
XCTAssertEqual(request.predicate, predicate)
|
XCTAssertEqual(request.predicate, NSPredicate(format: "%K == %@", "key", "value"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
B5931B4825124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4725124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift */; };
|
B5931B4825124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4725124940007DA58E /* Advanced.EvolutionDemo.V4.FromV3.swift */; };
|
||||||
B5931B4A25124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */; };
|
B5931B4A25124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */; };
|
||||||
B5931B4C251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */; };
|
B5931B4C251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */; };
|
||||||
B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911C24E5429200E7E8BD /* AppDelegate.swift */; };
|
B5A3911D24E5429200E7E8BD /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911C24E5429200E7E8BD /* DemoApp.swift */; };
|
||||||
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */; };
|
|
||||||
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3912024E5429200E7E8BD /* Menu.MainView.swift */; };
|
B5A3912124E5429200E7E8BD /* Menu.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3912024E5429200E7E8BD /* Menu.MainView.swift */; };
|
||||||
B5A3912324E5429300E7E8BD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912224E5429300E7E8BD /* Images.xcassets */; };
|
B5A3912324E5429300E7E8BD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912224E5429300E7E8BD /* Images.xcassets */; };
|
||||||
B5A3912624E5429300E7E8BD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */; };
|
B5A3912624E5429300E7E8BD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */; };
|
||||||
@@ -44,7 +43,6 @@
|
|||||||
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917424E6990200E7E8BD /* MapKit.framework */; };
|
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917424E6990200E7E8BD /* MapKit.framework */; };
|
||||||
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917624E6990700E7E8BD /* CoreLocation.framework */; };
|
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917624E6990700E7E8BD /* CoreLocation.framework */; };
|
||||||
B5A3917924E6991600E7E8BD /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917824E6991600E7E8BD /* SwiftUI.framework */; };
|
B5A3917924E6991600E7E8BD /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917824E6991600E7E8BD /* SwiftUI.framework */; };
|
||||||
B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917B24E6A76C00E7E8BD /* LazyView.swift */; };
|
|
||||||
B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */; };
|
B5A3917E24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */; };
|
||||||
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917F24E787D900E7E8BD /* InstructionsView.swift */; };
|
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3917F24E787D900E7E8BD /* InstructionsView.swift */; };
|
||||||
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */; };
|
B5A3918324E7A21800E7E8BD /* Modern.TimeZonesDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */; };
|
||||||
@@ -136,8 +134,7 @@
|
|||||||
B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.FromV4.swift; sourceTree = "<group>"; };
|
B5931B4925124993007DA58E /* Advanced.EvolutionDemo.V3.FromV4.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.FromV4.swift; sourceTree = "<group>"; };
|
||||||
B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV3.swift; sourceTree = "<group>"; };
|
B5931B4B251249EE007DA58E /* Advanced.EvolutionDemo.V2.FromV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV3.swift; sourceTree = "<group>"; };
|
||||||
B5A3911924E5429200E7E8BD /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
B5A3911924E5429200E7E8BD /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
B5A3911C24E5429200E7E8BD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
B5A3911C24E5429200E7E8BD /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = "<group>"; };
|
||||||
B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
|
||||||
B5A3912024E5429200E7E8BD /* Menu.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.MainView.swift; sourceTree = "<group>"; };
|
B5A3912024E5429200E7E8BD /* Menu.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.MainView.swift; sourceTree = "<group>"; };
|
||||||
B5A3912224E5429300E7E8BD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
B5A3912224E5429300E7E8BD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||||
B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
B5A3912524E5429300E7E8BD /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
@@ -159,7 +156,6 @@
|
|||||||
B5A3917424E6990200E7E8BD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; };
|
B5A3917424E6990200E7E8BD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
B5A3917624E6990700E7E8BD /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; };
|
B5A3917624E6990700E7E8BD /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/CoreLocation.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
B5A3917824E6991600E7E8BD /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
|
B5A3917824E6991600E7E8BD /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.6.sdk/System/Library/Frameworks/SwiftUI.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
B5A3917B24E6A76C00E7E8BD /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
|
||||||
B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.Geocoder.swift; sourceTree = "<group>"; };
|
B5A3917D24E7728400E7E8BD /* Modern.PlacemarksDemo.Geocoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PlacemarksDemo.Geocoder.swift; sourceTree = "<group>"; };
|
||||||
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsView.swift; sourceTree = "<group>"; };
|
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionsView.swift; sourceTree = "<group>"; };
|
||||||
B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.swift; sourceTree = "<group>"; };
|
B5A3918224E7A21800E7E8BD /* Modern.TimeZonesDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.TimeZonesDemo.swift; sourceTree = "<group>"; };
|
||||||
@@ -257,8 +253,7 @@
|
|||||||
B5A3911B24E5429200E7E8BD /* Sources */ = {
|
B5A3911B24E5429200E7E8BD /* Sources */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B5A3911C24E5429200E7E8BD /* AppDelegate.swift */,
|
B5A3911C24E5429200E7E8BD /* DemoApp.swift */,
|
||||||
B5A3911E24E5429200E7E8BD /* SceneDelegate.swift */,
|
|
||||||
B5A3915524E6858A00E7E8BD /* Demos */,
|
B5A3915524E6858A00E7E8BD /* Demos */,
|
||||||
B5A3917A24E6A75F00E7E8BD /* Helpers */,
|
B5A3917A24E6A75F00E7E8BD /* Helpers */,
|
||||||
);
|
);
|
||||||
@@ -371,7 +366,6 @@
|
|||||||
B5A3917A24E6A75F00E7E8BD /* Helpers */ = {
|
B5A3917A24E6A75F00E7E8BD /* Helpers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B5A3917B24E6A76C00E7E8BD /* LazyView.swift */,
|
|
||||||
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
|
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
|
||||||
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */,
|
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */,
|
||||||
B5A3915424E6857F00E7E8BD /* Menu */,
|
B5A3915424E6857F00E7E8BD /* Menu */,
|
||||||
@@ -668,14 +662,12 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B5A543F624FBF13A000DC5E3 /* Advanced.swift in Sources */,
|
B5A543F624FBF13A000DC5E3 /* Advanced.swift in Sources */,
|
||||||
B5A3911D24E5429200E7E8BD /* AppDelegate.swift in Sources */,
|
B5A3911D24E5429200E7E8BD /* DemoApp.swift in Sources */,
|
||||||
B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */,
|
B5A3915924E685EC00E7E8BD /* Classic.swift in Sources */,
|
||||||
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */,
|
B5E32C9024FA41F9003F46AD /* ImageDownloader.swift in Sources */,
|
||||||
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */,
|
B5A3918024E787D900E7E8BD /* InstructionsView.swift in Sources */,
|
||||||
B5A3917C24E6A76C00E7E8BD /* LazyView.swift in Sources */,
|
|
||||||
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */,
|
B5A3913424E6170500E7E8BD /* Menu.swift in Sources */,
|
||||||
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */,
|
B5A3915B24E685FE00E7E8BD /* Modern.swift in Sources */,
|
||||||
B5A3911F24E5429200E7E8BD /* SceneDelegate.swift in Sources */,
|
|
||||||
B5A543FB2504840E000DC5E3 /* Advanced.EvolutionDemo.swift in Sources */,
|
B5A543FB2504840E000DC5E3 /* Advanced.EvolutionDemo.swift in Sources */,
|
||||||
B5A543D724FB7478000DC5E3 /* Classic.ColorsDemo.swift in Sources */,
|
B5A543D724FB7478000DC5E3 /* Classic.ColorsDemo.swift in Sources */,
|
||||||
B5A543DB24FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld in Sources */,
|
B5A543DB24FB7513000DC5E3 /* Classic.ColorsDemo.xcdatamodeld in Sources */,
|
||||||
@@ -801,7 +793,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -837,7 +829,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -897,6 +889,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = Info.plist;
|
INFOPLIST_FILE = Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -909,7 +902,7 @@
|
|||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -964,6 +957,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = Info.plist;
|
INFOPLIST_FILE = Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -975,7 +969,7 @@
|
|||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 6.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,29 +20,8 @@
|
|||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSceneManifest</key>
|
|
||||||
<dict>
|
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
|
||||||
<false/>
|
|
||||||
<key>UISceneConfigurations</key>
|
|
||||||
<dict>
|
|
||||||
<key>UIWindowSceneSessionRoleApplication</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>UISceneConfigurationName</key>
|
|
||||||
<string>Default Configuration</string>
|
|
||||||
<key>UISceneDelegateClassName</key>
|
|
||||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
|
||||||
<array>
|
|
||||||
<string>armv7</string>
|
|
||||||
</array>
|
|
||||||
<key>UIStatusBarTintParameters</key>
|
<key>UIStatusBarTintParameters</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UINavigationBar</key>
|
<key>UINavigationBar</key>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// Demo
|
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
// MARK: - AppDelegate
|
|
||||||
|
|
||||||
@UIApplicationMain
|
|
||||||
@objc final class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
||||||
|
|
||||||
// MARK: UIApplicationDelegate
|
|
||||||
|
|
||||||
@objc dynamic func application(
|
|
||||||
_ application: UIApplication,
|
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
|
||||||
) -> Bool {
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc dynamic func application(
|
|
||||||
_ application: UIApplication,
|
|
||||||
configurationForConnecting connectingSceneSession: UISceneSession,
|
|
||||||
options: UIScene.ConnectionOptions
|
|
||||||
) -> UISceneConfiguration {
|
|
||||||
|
|
||||||
return UISceneConfiguration(
|
|
||||||
name: "Default Configuration",
|
|
||||||
sessionRole: connectingSceneSession.role
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Demo
|
||||||
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct DemoApp: App {
|
||||||
|
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
Menu.MainView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ protocol Advanced_EvolutionDemo_CreatureType: DynamicObject, CustomStringConvert
|
|||||||
|
|
||||||
var dnaCode: Int64 { get set }
|
var dnaCode: Int64 { get set }
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource
|
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource
|
||||||
|
|
||||||
static func count(in transaction: BaseDataTransaction) throws -> Int
|
static func count(in transaction: BaseDataTransaction) throws -> Int
|
||||||
|
|||||||
+21
-60
@@ -3,7 +3,7 @@
|
|||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import Combine
|
import Observation
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Advanced.EvolutionDemo
|
// MARK: - Advanced.EvolutionDemo
|
||||||
@@ -15,11 +15,13 @@ extension Advanced.EvolutionDemo {
|
|||||||
/**
|
/**
|
||||||
A type-erasing adapter to support different `ListPublisher` types
|
A type-erasing adapter to support different `ListPublisher` types
|
||||||
*/
|
*/
|
||||||
final class CreaturesDataSource: ObservableObject {
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
final class CreaturesDataSource {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
init<T: NSManagedObject & Advanced.EvolutionDemo.CreatureType>(
|
init<T: DynamicObject & Advanced.EvolutionDemo.CreatureType>(
|
||||||
listPublisher: ListPublisher<T>,
|
listPublisher: ListPublisher<T>,
|
||||||
dataStack: DataStack
|
dataStack: DataStack
|
||||||
) {
|
) {
|
||||||
@@ -69,75 +71,23 @@ extension Advanced.EvolutionDemo {
|
|||||||
completion: { _ in }
|
completion: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
listPublisher.addObserver(self) { [weak self] _ in
|
||||||
|
|
||||||
self?.objectWillChange.send()
|
Task { @MainActor in
|
||||||
|
self?.refreshID += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init<T: CoreStoreObject & Advanced.EvolutionDemo.CreatureType>(
|
|
||||||
listPublisher: ListPublisher<T>,
|
|
||||||
dataStack: DataStack
|
|
||||||
) {
|
|
||||||
|
|
||||||
self.numberOfItems = {
|
|
||||||
listPublisher.snapshot.numberOfItems
|
|
||||||
}
|
|
||||||
self.itemDescriptionAtIndex = { index in
|
|
||||||
listPublisher.snapshot[index].object?.description
|
|
||||||
}
|
|
||||||
self.addItems = { count in
|
|
||||||
|
|
||||||
dataStack.perform(
|
|
||||||
asynchronous: { transaction in
|
|
||||||
|
|
||||||
let nextDNACode = try transaction.fetchCount(From<T>())
|
|
||||||
for offset in 0 ..< count {
|
|
||||||
|
|
||||||
let object = transaction.create(Into<T>())
|
|
||||||
object.dnaCode = .init(nextDNACode + offset)
|
|
||||||
object.mutate(in: transaction)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
completion: { _ in }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
self.mutateItemAtIndex = { index in
|
|
||||||
|
|
||||||
let object = listPublisher.snapshot[index]
|
|
||||||
dataStack.perform(
|
|
||||||
asynchronous: { transaction in
|
|
||||||
|
|
||||||
object
|
|
||||||
.asEditable(in: transaction)?
|
|
||||||
.mutate(in: transaction)
|
|
||||||
},
|
|
||||||
completion: { _ in }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
self.deleteAllItems = {
|
|
||||||
|
|
||||||
dataStack.perform(
|
|
||||||
asynchronous: { transaction in
|
|
||||||
|
|
||||||
try transaction.deleteAll(From<T>())
|
|
||||||
},
|
|
||||||
completion: { _ in }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
listPublisher.addObserver(self) { [weak self] (listPublisher) in
|
|
||||||
|
|
||||||
self?.objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func numberOfCreatures() -> Int {
|
func numberOfCreatures() -> Int {
|
||||||
|
|
||||||
|
_ = self.refreshID
|
||||||
return self.numberOfItems()
|
return self.numberOfItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
func creatureDescription(at index: Int) -> String? {
|
func creatureDescription(at index: Int) -> String? {
|
||||||
|
|
||||||
|
_ = self.refreshID
|
||||||
return self.itemDescriptionAtIndex(index)
|
return self.itemDescriptionAtIndex(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,10 +109,21 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
|
private var refreshID: Int = 0
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
private let numberOfItems: () -> Int
|
private let numberOfItems: () -> Int
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
private let itemDescriptionAtIndex: (Int) -> String?
|
private let itemDescriptionAtIndex: (Int) -> String?
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
private let mutateItemAtIndex: (Int) -> Void
|
private let mutateItemAtIndex: (Int) -> Void
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
private let addItems: (Int) -> Void
|
private let addItems: (Int) -> Void
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
private let deleteAllItems: () -> Void
|
private let deleteAllItems: () -> Void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,20 +24,25 @@ extension Advanced.EvolutionDemo {
|
|||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Text(self.description ?? "")
|
Text(self.description ?? "")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
action: self.mutate,
|
action: self.mutate,
|
||||||
label: {
|
label: {
|
||||||
|
|
||||||
Text("Mutate")
|
Text("Mutate")
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
.disabled(self.description == nil)
|
.disabled(self.description == nil)
|
||||||
}
|
}
|
||||||
@@ -49,27 +54,3 @@ extension Advanced.EvolutionDemo {
|
|||||||
fileprivate let mutate: () -> Void
|
fileprivate let mutate: () -> Void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Advanced_EvolutionDemo_ItemView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
Advanced.EvolutionDemo.ItemView(
|
|
||||||
description: """
|
|
||||||
dnaCode: 123
|
|
||||||
numberOfLimbs: 4
|
|
||||||
hasVertebrae: true
|
|
||||||
hasHead: true
|
|
||||||
hasTail: true
|
|
||||||
habitat: land
|
|
||||||
hasWings: false
|
|
||||||
""",
|
|
||||||
mutate: {}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -13,25 +13,6 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
struct ListView: View {
|
struct ListView: View {
|
||||||
|
|
||||||
// MARK: View
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
let dataSource = self.dataSource
|
|
||||||
return List {
|
|
||||||
ForEach(0 ..< dataSource.numberOfCreatures(), id: \.self) { (index) in
|
|
||||||
Advanced.EvolutionDemo.ItemView(
|
|
||||||
description: dataSource.creatureDescription(at: index),
|
|
||||||
mutate: {
|
|
||||||
|
|
||||||
dataSource.mutate(at: index)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.listStyle(PlainListStyle())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@@ -46,54 +27,32 @@ extension Advanced.EvolutionDemo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: View
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
|
||||||
|
List {
|
||||||
|
ForEach(0 ..< self.dataSource.numberOfCreatures(), id: \.self) { index in
|
||||||
|
|
||||||
|
Advanced.EvolutionDemo.ItemView(
|
||||||
|
description: self.dataSource.creatureDescription(at: index),
|
||||||
|
mutate: {
|
||||||
|
|
||||||
|
self.dataSource.mutate(at: index)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let period: Advanced.EvolutionDemo.GeologicalPeriod
|
private let period: Advanced.EvolutionDemo.GeologicalPeriod
|
||||||
|
|
||||||
private let dataStack: DataStack
|
private let dataStack: DataStack
|
||||||
|
|
||||||
@ObservedObject
|
private let dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||||
private var dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Advanced_EvolutionDemo_ListView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let dataStack = DataStack(
|
|
||||||
CoreStoreSchema(
|
|
||||||
modelVersion: Advanced.EvolutionDemo.V4.name,
|
|
||||||
entities: [
|
|
||||||
Entity<Advanced.EvolutionDemo.V4.Creature>("Creature")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
try! dataStack.addStorageAndWait(
|
|
||||||
SQLiteStore(fileName: "Advanced.EvolutionDemo.ListView.Preview.sqlite")
|
|
||||||
)
|
|
||||||
try! dataStack.perform(
|
|
||||||
synchronous: { transaction in
|
|
||||||
|
|
||||||
for dnaCode in 0 ..< 10 as Range<Int64> {
|
|
||||||
|
|
||||||
let object = transaction.create(Into<Advanced.EvolutionDemo.V4.Creature>())
|
|
||||||
object.dnaCode = dnaCode
|
|
||||||
object.mutate(in: transaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return Advanced.EvolutionDemo.ListView(
|
|
||||||
period: .ageOfMammals,
|
|
||||||
dataStack: dataStack,
|
|
||||||
dataSource: Advanced.EvolutionDemo.V4.Creature.dataSource(in: dataStack)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -13,66 +13,49 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var migrator = Advanced.EvolutionDemo.Migrator()
|
||||||
|
|
||||||
|
|
||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let migrator = self.migrator
|
|
||||||
let listView: AnyView
|
VStack(spacing: 0) {
|
||||||
|
|
||||||
|
HStack(alignment: .center, spacing: 0) {
|
||||||
|
|
||||||
|
Text("Age of")
|
||||||
|
.padding(.trailing)
|
||||||
|
|
||||||
|
Picker(selection: $migrator.currentPeriod, label: EmptyView()) {
|
||||||
|
|
||||||
|
ForEach(Advanced.EvolutionDemo.GeologicalPeriod.allCases, id: \.self) { period in
|
||||||
|
|
||||||
|
Text(period.description).tag(period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
if let current = migrator.current {
|
if let current = migrator.current {
|
||||||
|
|
||||||
listView = AnyView(
|
|
||||||
Advanced.EvolutionDemo.ListView(
|
Advanced.EvolutionDemo.ListView(
|
||||||
period: current.period,
|
period: current.period,
|
||||||
dataStack: current.dataStack,
|
dataStack: current.dataStack,
|
||||||
dataSource: current.dataSource
|
dataSource: current.dataSource
|
||||||
)
|
)
|
||||||
)
|
.ignoresSafeArea(.container, edges: .vertical)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
listView = AnyView(
|
|
||||||
Advanced.EvolutionDemo.ProgressView(progress: migrator.progress)
|
Advanced.EvolutionDemo.ProgressView(progress: migrator.progress)
|
||||||
)
|
.ignoresSafeArea(.container, edges: .vertical)
|
||||||
}
|
|
||||||
|
|
||||||
return VStack(spacing: 0) {
|
|
||||||
HStack(alignment: .center, spacing: 0) {
|
|
||||||
Text("Age of")
|
|
||||||
.padding(.trailing)
|
|
||||||
Picker(selection: self.$migrator.currentPeriod, label: EmptyView()) {
|
|
||||||
ForEach(Advanced.EvolutionDemo.GeologicalPeriod.allCases, id: \.self) { period in
|
|
||||||
Text(period.description).tag(period)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
.navigationTitle("Evolution")
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
listView
|
|
||||||
.edgesIgnoringSafeArea(.vertical)
|
|
||||||
}
|
|
||||||
.navigationBarTitle("Evolution")
|
|
||||||
.disabled(migrator.isBusy || migrator.current == nil)
|
.disabled(migrator.isBusy || migrator.current == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
private var migrator: Advanced.EvolutionDemo.Migrator = .init()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Advanced_EvolutionDemo_MainView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
Advanced.EvolutionDemo.MainView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Observation
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Advanced.EvolutionDemo
|
// MARK: - Advanced.EvolutionDemo
|
||||||
@@ -13,7 +13,9 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
// MARK: - Advanced.EvolutionDemo.Migrator
|
// MARK: - Advanced.EvolutionDemo.Migrator
|
||||||
|
|
||||||
final class Migrator: ObservableObject {
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
final class Migrator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
⭐️ Sample 1: Creating a complex `DataStack` that contains all schema histories. The `exactCurrentModelVersion` will specify the target version (if required), and `migrationChain` will provide the upgrade/downgrade progressive migration path.
|
⭐️ Sample 1: Creating a complex `DataStack` that contains all schema histories. The `exactCurrentModelVersion` will specify the target version (if required), and `migrationChain` will provide the upgrade/downgrade progressive migration path.
|
||||||
@@ -79,7 +81,7 @@ extension Advanced.EvolutionDemo {
|
|||||||
private func findCurrentVersion() -> ModelVersion {
|
private func findCurrentVersion() -> ModelVersion {
|
||||||
|
|
||||||
let allVersions = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
let allVersions = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
||||||
.map({ $0.version })
|
.map(\.version)
|
||||||
|
|
||||||
// Since we are only interested in finding current version, we'll assume an upgrading `MigrationChain`
|
// Since we are only interested in finding current version, we'll assume an upgrading `MigrationChain`
|
||||||
let dataStack = self.createDataStack(
|
let dataStack = self.createDataStack(
|
||||||
@@ -110,13 +112,7 @@ extension Advanced.EvolutionDemo {
|
|||||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||||
dataStack: DataStack,
|
dataStack: DataStack,
|
||||||
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
|
||||||
)? {
|
)?
|
||||||
|
|
||||||
willSet {
|
|
||||||
|
|
||||||
self.objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private(set) var isBusy: Bool = false
|
private(set) var isBusy: Bool = false
|
||||||
|
|
||||||
@@ -151,8 +147,6 @@ extension Advanced.EvolutionDemo {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.objectWillChange.send()
|
|
||||||
|
|
||||||
self.isBusy = true
|
self.isBusy = true
|
||||||
|
|
||||||
// explicitly trigger `NSPersistentStore` cleanup by deallocating the `DataStack`
|
// explicitly trigger `NSPersistentStore` cleanup by deallocating the `DataStack`
|
||||||
@@ -166,7 +160,7 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
case (let currentVersion?, let newVersion):
|
case (let currentVersion?, let newVersion):
|
||||||
let upgradeMigrationChain = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
let upgradeMigrationChain = Advanced.EvolutionDemo.GeologicalPeriod.allCases
|
||||||
.map({ $0.version })
|
.map(\.version)
|
||||||
let currentVersionIndex = upgradeMigrationChain.firstIndex(of: currentVersion)!
|
let currentVersionIndex = upgradeMigrationChain.firstIndex(of: currentVersion)!
|
||||||
let newVersionIndex = upgradeMigrationChain.firstIndex(of: newVersion)!
|
let newVersionIndex = upgradeMigrationChain.firstIndex(of: newVersion)!
|
||||||
|
|
||||||
@@ -181,13 +175,37 @@ extension Advanced.EvolutionDemo {
|
|||||||
migrationChain: migrationChain
|
migrationChain: migrationChain
|
||||||
)
|
)
|
||||||
|
|
||||||
let completion = { [weak self] () -> Void in
|
self.progress = dataStack.addStorage(
|
||||||
|
self.accessSQLiteStore(),
|
||||||
|
completion: { [weak self] result in
|
||||||
|
|
||||||
guard let self = self else {
|
guard let self else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.objectWillChange.send()
|
guard case .success = result else {
|
||||||
|
|
||||||
|
self.isBusy = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.progress == nil {
|
||||||
|
|
||||||
|
self.spawnCreatures(in: dataStack, period: period) { [weak self] in
|
||||||
|
self?.completeSelection(period: period, dataStack: dataStack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
self.completeSelection(period: period, dataStack: dataStack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func completeSelection(
|
||||||
|
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||||
|
dataStack: DataStack
|
||||||
|
) {
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
self.isBusy = false
|
self.isBusy = false
|
||||||
@@ -198,42 +216,17 @@ extension Advanced.EvolutionDemo {
|
|||||||
dataSource: period.creatureType.dataSource(in: dataStack)
|
dataSource: period.creatureType.dataSource(in: dataStack)
|
||||||
)
|
)
|
||||||
self.currentPeriod = period
|
self.currentPeriod = period
|
||||||
}
|
self.progress = nil
|
||||||
|
|
||||||
self.progress = dataStack.addStorage(
|
|
||||||
self.accessSQLiteStore(),
|
|
||||||
completion: { [weak self] result in
|
|
||||||
|
|
||||||
guard let self = self else {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard case .success = result else {
|
|
||||||
|
|
||||||
self.objectWillChange.send()
|
|
||||||
self.isBusy = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if self.progress == nil {
|
|
||||||
|
|
||||||
self.spawnCreatures(in: dataStack, period: period, completion: completion)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
completion()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func spawnCreatures(
|
private func spawnCreatures(
|
||||||
in dataStack: DataStack,
|
in dataStack: DataStack,
|
||||||
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
period: Advanced.EvolutionDemo.GeologicalPeriod,
|
||||||
completion: @escaping () -> Void
|
completion: @escaping @MainActor @Sendable () -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
dataStack.perform(
|
dataStack.perform(
|
||||||
asynchronous: { (transaction) in
|
asynchronous: { transaction in
|
||||||
|
|
||||||
let creatureType = period.creatureType
|
let creatureType = period.creatureType
|
||||||
for dnaCode in try creatureType.count(in: transaction) ..< 10000 {
|
for dnaCode in try creatureType.count(in: transaction) ..< 10000 {
|
||||||
@@ -243,7 +236,10 @@ extension Advanced.EvolutionDemo {
|
|||||||
object.mutate(in: transaction)
|
object.mutate(in: transaction)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
completion: { _ in completion() }
|
completion: { _ in
|
||||||
|
|
||||||
|
completion()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-43
@@ -2,6 +2,7 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// MARK: - Advanced.EvolutionDemo
|
// MARK: - Advanced.EvolutionDemo
|
||||||
@@ -16,7 +17,8 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
init(progress: Progress?) {
|
init(progress: Progress?) {
|
||||||
|
|
||||||
self.progressObserver = .init(progress)
|
self.progress = progress
|
||||||
|
self._progressObserver = State(initialValue: .init(progress))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -24,30 +26,28 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
guard self.progressObserver.isMigrating else {
|
Group {
|
||||||
|
|
||||||
|
if self.progressObserver.isMigrating {
|
||||||
|
|
||||||
return AnyView(
|
|
||||||
VStack(alignment: .center) {
|
|
||||||
Text("Preparing creatures...")
|
|
||||||
.padding()
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return AnyView(
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
Text("Migrating: \(self.progressObserver.localizedDescription)")
|
Text("Migrating: \(self.progressObserver.localizedDescription)")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.padding([.top, .horizontal])
|
.padding([.top, .horizontal])
|
||||||
|
|
||||||
Text("Progressive step: \(self.progressObserver.localizedAdditionalDescription)")
|
Text("Progressive step: \(self.progressObserver.localizedAdditionalDescription)")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
|
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||||
.fill(Color.gray.opacity(0.2))
|
.fill(Color.gray.opacity(0.2))
|
||||||
.frame(width: geometry.size.width, height: 8)
|
.frame(width: geometry.size.width, height: 8)
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||||
.fill(Color.blue)
|
.fill(Color.blue)
|
||||||
.frame(
|
.frame(
|
||||||
@@ -59,22 +59,41 @@ extension Advanced.EvolutionDemo {
|
|||||||
}
|
}
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
)
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
VStack(alignment: .center) {
|
||||||
|
Text("Preparing creatures...")
|
||||||
|
.padding()
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.task(id: self.progress.map { ObjectIdentifier($0) }) {
|
||||||
|
|
||||||
|
self.progressObserver = .init(self.progress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: Private
|
||||||
|
|
||||||
@ObservedObject
|
private let progress: Progress?
|
||||||
|
|
||||||
|
@State
|
||||||
private var progressObserver: ProgressObserver
|
private var progressObserver: ProgressObserver
|
||||||
|
|
||||||
|
|
||||||
// MARK: - ProgressObserver
|
// MARK: - ProgressObserver
|
||||||
|
|
||||||
fileprivate final class ProgressObserver: ObservableObject {
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
fileprivate final class ProgressObserver {
|
||||||
|
|
||||||
private(set) var fractionCompleted: CGFloat = 0
|
private(set) var fractionCompleted: CGFloat = 0
|
||||||
private(set) var localizedDescription: String = ""
|
private(set) var localizedDescription: String = ""
|
||||||
@@ -82,45 +101,32 @@ extension Advanced.EvolutionDemo {
|
|||||||
|
|
||||||
var isMigrating: Bool {
|
var isMigrating: Bool {
|
||||||
|
|
||||||
return self.progress != nil
|
self.progress != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ progress: Progress?) {
|
init(_ progress: Progress?) {
|
||||||
|
|
||||||
self.progress = progress
|
self.progress = progress
|
||||||
|
self.syncValues(from: progress)
|
||||||
|
|
||||||
progress?.setProgressHandler { [weak self] (progess) in
|
progress?.setProgressHandler { [weak self] progress in
|
||||||
|
|
||||||
guard let self = self else {
|
self?.syncValues(from: progress)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
self.objectWillChange.send()
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
|
private let progress: Progress?
|
||||||
|
|
||||||
|
private func syncValues(from progress: Progress?) {
|
||||||
|
|
||||||
self.fractionCompleted = CGFloat(progress?.fractionCompleted ?? 0)
|
self.fractionCompleted = CGFloat(progress?.fractionCompleted ?? 0)
|
||||||
self.localizedDescription = progress?.localizedDescription ?? ""
|
self.localizedDescription = progress?.localizedDescription ?? ""
|
||||||
self.localizedAdditionalDescription = progress?.localizedAdditionalDescription ?? ""
|
self.localizedAdditionalDescription = progress?.localizedAdditionalDescription ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
private let progress: Progress?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Advanced_EvolutionDemo_ProgressView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
let progress = Progress(totalUnitCount: 10)
|
|
||||||
progress.completedUnitCount = 3
|
|
||||||
return Advanced.EvolutionDemo.ProgressView(
|
|
||||||
progress: progress
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ final class Advanced_EvolutionDemo_V1_Creature: NSManagedObject, Advanced.Evolut
|
|||||||
|
|
||||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ final class Advanced_EvolutionDemo_V2_Creature: NSManagedObject, Advanced.Evolut
|
|||||||
|
|
||||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ extension Advanced.EvolutionDemo.V3 {
|
|||||||
|
|
||||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ extension Advanced.EvolutionDemo.V4 {
|
|||||||
|
|
||||||
// MARK: Advanced.EvolutionDemo.CreatureType
|
// MARK: Advanced.EvolutionDemo.CreatureType
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Combine
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -48,32 +47,3 @@ extension Classic.ColorsDemo {
|
|||||||
private let palette: ObjectMonitor<Classic.ColorsDemo.Palette>
|
private let palette: ObjectMonitor<Classic.ColorsDemo.Palette>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Classic_ColorsDemo_DetailView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
try! Classic.ColorsDemo.dataStack.perform(
|
|
||||||
synchronous: { transaction in
|
|
||||||
|
|
||||||
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
|
||||||
palette.setRandomHue()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Classic.ColorsDemo.DetailView(
|
|
||||||
Classic.ColorsDemo.dataStack.monitorObject(
|
|
||||||
Classic.ColorsDemo.palettesMonitor[0, 0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -55,13 +55,10 @@ extension Classic.ColorsDemo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Classic_ColorsDemo_ListView_Preview: PreviewProvider {
|
// MARK: - Preview
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
#Preview {
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let minimumSamples = 10
|
let minimumSamples = 10
|
||||||
try! Classic.ColorsDemo.dataStack.perform(
|
try! Classic.ColorsDemo.dataStack.perform(
|
||||||
@@ -84,6 +81,3 @@ struct _Demo_Classic_ColorsDemo_ListView_Preview: PreviewProvider {
|
|||||||
onPaletteTapped: { _ in }
|
onPaletteTapped: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
// MARK: - Classic.ColorsDemo
|
// MARK: - Classic.ColorsDemo
|
||||||
@@ -19,7 +20,7 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
let listMonitor = Classic.ColorsDemo.palettesMonitor
|
let listMonitor = Classic.ColorsDemo.palettesMonitor
|
||||||
self.listMonitor = listMonitor
|
self.listMonitor = listMonitor
|
||||||
self.listHelper = .init(listMonitor: listMonitor)
|
self._listHelper = State(initialValue: .init(listMonitor: listMonitor))
|
||||||
self._filter = Binding(
|
self._filter = Binding(
|
||||||
get: { Classic.ColorsDemo.filter },
|
get: { Classic.ColorsDemo.filter },
|
||||||
set: { Classic.ColorsDemo.filter = $0 }
|
set: { Classic.ColorsDemo.filter = $0 }
|
||||||
@@ -30,59 +31,47 @@ extension Classic.ColorsDemo {
|
|||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let detailView: AnyView
|
VStack(spacing: 0) {
|
||||||
if let selectedObject = self.listHelper.selectedObject() {
|
Classic.ColorsDemo.ListView(
|
||||||
|
listMonitor: self.listMonitor,
|
||||||
detailView = AnyView(
|
|
||||||
Classic.ColorsDemo.DetailView(selectedObject)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
detailView = AnyView(EmptyView())
|
|
||||||
}
|
|
||||||
let listMonitor = self.listMonitor
|
|
||||||
return VStack(spacing: 0) {
|
|
||||||
Classic.ColorsDemo.ListView
|
|
||||||
.init(
|
|
||||||
listMonitor: listMonitor,
|
|
||||||
onPaletteTapped: {
|
onPaletteTapped: {
|
||||||
|
|
||||||
self.listHelper.setSelectedPalette($0)
|
self.listHelper.setSelectedPalette($0)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.navigationBarTitle(
|
|
||||||
Text("Colors (\(self.listHelper.count) objects)")
|
|
||||||
)
|
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
.frame(minHeight: 0, maxHeight: .infinity)
|
||||||
.edgesIgnoringSafeArea(.vertical)
|
.ignoresSafeArea(.container, edges: .vertical)
|
||||||
detailView
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
if let selectedObject = self.listHelper.selectedObject() {
|
||||||
|
Classic.ColorsDemo.DetailView(selectedObject)
|
||||||
|
.ignoresSafeArea()
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
.frame(minHeight: 0, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
.navigationBarItems(
|
}
|
||||||
leading: HStack {
|
.navigationTitle("Colors (\(self.listHelper.count) objects)")
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItemGroup(placement: .topBarLeading) {
|
||||||
EditButton()
|
EditButton()
|
||||||
Button(
|
Button("Clear") {
|
||||||
action: { self.clearColors() },
|
|
||||||
label: { Text("Clear") }
|
self.clearColors()
|
||||||
)
|
}
|
||||||
},
|
}
|
||||||
trailing: HStack {
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
||||||
Button(
|
Button(self.filter.rawValue) {
|
||||||
action: { self.changeFilter() },
|
|
||||||
label: { Text(self.filter.rawValue) }
|
self.changeFilter()
|
||||||
)
|
}
|
||||||
Button(
|
Button("Shuffle") {
|
||||||
action: { self.shuffleColors() },
|
|
||||||
label: { Text("Shuffle") }
|
self.shuffleColors()
|
||||||
)
|
}
|
||||||
Button(
|
Button("Add") {
|
||||||
action: { self.addColor() },
|
|
||||||
label: { Text("Add") }
|
self.addColor()
|
||||||
)
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -90,7 +79,7 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
|
private let listMonitor: ListMonitor<Classic.ColorsDemo.Palette>
|
||||||
|
|
||||||
@ObservedObject
|
@State
|
||||||
private var listHelper: ListHelper
|
private var listHelper: ListHelper
|
||||||
|
|
||||||
@Binding
|
@Binding
|
||||||
@@ -140,7 +129,9 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
// MARK: - Classic.ColorsDemo.MainView.ListHelper
|
// MARK: - Classic.ColorsDemo.MainView.ListHelper
|
||||||
|
|
||||||
fileprivate final class ListHelper: ObservableObject, ListObjectObserver {
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
fileprivate final class ListHelper: ListObjectObserver {
|
||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: FilePrivate
|
||||||
|
|
||||||
@@ -154,8 +145,7 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
fileprivate func selectedObject() -> ObjectMonitor<Classic.ColorsDemo.Palette>? {
|
fileprivate func selectedObject() -> ObjectMonitor<Classic.ColorsDemo.Palette>? {
|
||||||
|
|
||||||
return self.selectedPalette.flatMap {
|
self.selectedPalette.flatMap {
|
||||||
|
|
||||||
guard !$0.isDeleted else {
|
guard !$0.isDeleted else {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -170,8 +160,7 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.objectWillChange.send()
|
if let palette, !palette.isDeleted {
|
||||||
if let palette = palette, !palette.isDeleted {
|
|
||||||
|
|
||||||
self.selectedPalette = palette
|
self.selectedPalette = palette
|
||||||
}
|
}
|
||||||
@@ -186,27 +175,38 @@ extension Classic.ColorsDemo {
|
|||||||
|
|
||||||
typealias ListEntityType = Classic.ColorsDemo.Palette
|
typealias ListEntityType = Classic.ColorsDemo.Palette
|
||||||
|
|
||||||
func listMonitorDidChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
nonisolated func listMonitorDidChange(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>) {
|
||||||
|
let count = monitor.numberOfObjects()
|
||||||
|
|
||||||
self.objectWillChange.send()
|
Task { @MainActor in
|
||||||
self.count = monitor.numberOfObjects()
|
self.count = count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listMonitorDidRefetch(_ monitor: ListMonitor<ListEntityType>) {
|
nonisolated func listMonitorDidRefetch(_ monitor: ListMonitor<ListEntityType>) {
|
||||||
|
let count = monitor.numberOfObjects()
|
||||||
|
|
||||||
self.objectWillChange.send()
|
Task { @MainActor in
|
||||||
self.count = monitor.numberOfObjects()
|
self.count = count
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: ListObjectObserver
|
// MARK: ListObjectObserver
|
||||||
|
|
||||||
func listMonitor(_ monitor: ListMonitor<Classic.ColorsDemo.Palette>, didDeleteObject object: Classic.ColorsDemo.Palette, fromIndexPath indexPath: IndexPath) {
|
nonisolated func listMonitor(
|
||||||
|
_ monitor: ListMonitor<Classic.ColorsDemo.Palette>,
|
||||||
|
didDeleteObject object: Classic.ColorsDemo.Palette,
|
||||||
|
fromIndexPath indexPath: IndexPath
|
||||||
|
) {
|
||||||
|
let deletedObjectURI = object.objectID.uriRepresentation()
|
||||||
|
|
||||||
if self.selectedPalette == object {
|
Task { @MainActor in
|
||||||
|
if self.selectedPalette?.objectID.uriRepresentation() == deletedObjectURI {
|
||||||
|
|
||||||
self.setSelectedPalette(nil)
|
self.setSelectedPalette(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
@@ -215,33 +215,3 @@ extension Classic.ColorsDemo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Classic_ColorsDemo_MainView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let minimumSamples = 10
|
|
||||||
try! Classic.ColorsDemo.dataStack.perform(
|
|
||||||
synchronous: { transaction in
|
|
||||||
|
|
||||||
let missing = minimumSamples
|
|
||||||
- (try transaction.fetchCount(From<Classic.ColorsDemo.Palette>()))
|
|
||||||
guard missing > 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _ in 0..<missing {
|
|
||||||
|
|
||||||
let palette = transaction.create(Into<Classic.ColorsDemo.Palette>())
|
|
||||||
palette.setRandomHue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return Classic.ColorsDemo.MainView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ extension Classic {
|
|||||||
return dataStack
|
return dataStack
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static let palettesMonitor: ListMonitor<Classic.ColorsDemo.Palette> = Classic.ColorsDemo.dataStack.monitorSectionedList(
|
static let palettesMonitor: ListMonitor<Classic.ColorsDemo.Palette> = Classic.ColorsDemo.dataStack.monitorSectionedList(
|
||||||
From<Classic.ColorsDemo.Palette>()
|
From<Classic.ColorsDemo.Palette>()
|
||||||
.sectionBy(\.colorGroup)
|
.sectionBy(\.colorGroup)
|
||||||
@@ -45,6 +46,7 @@ extension Classic {
|
|||||||
.orderBy(.ascending(\.hue))
|
.orderBy(.ascending(\.hue))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static var filter: Classic.ColorsDemo.Filter = .all {
|
static var filter: Classic.ColorsDemo.Filter = .all {
|
||||||
|
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ extension Modern.ColorsDemo {
|
|||||||
_ listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
|
_ listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
|
||||||
_ onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
|
_ onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
|
||||||
) -> ListView,
|
) -> ListView,
|
||||||
detailView: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> DetailView) {
|
detailView: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> DetailView
|
||||||
|
) {
|
||||||
|
|
||||||
self.listView = listView
|
self.listView = listView
|
||||||
self.detailView = detailView
|
self.detailView = detailView
|
||||||
@@ -30,42 +31,51 @@ extension Modern.ColorsDemo {
|
|||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
return VStack(spacing: 0) {
|
|
||||||
self.listView(self.$palettes, { self.selectedPalette = $0 })
|
|
||||||
.navigationBarTitle(
|
|
||||||
Text("Colors (\(self.palettes.count) objects)")
|
|
||||||
)
|
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
|
||||||
self.selectedPalette.map {
|
|
||||||
|
|
||||||
self.detailView($0)
|
VStack(spacing: 0) {
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
|
self.listView(
|
||||||
|
self.$palettes,
|
||||||
|
{
|
||||||
|
self.selectedPalette = $0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.frame(minHeight: 0, maxHeight: .infinity)
|
||||||
|
|
||||||
|
if let selectedPalette = self.selectedPalette {
|
||||||
|
|
||||||
|
self.detailView(selectedPalette)
|
||||||
|
.ignoresSafeArea()
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
.frame(minHeight: 0, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarItems(
|
.navigationTitle("Colors (\(self.palettes.count) objects)")
|
||||||
leading: HStack {
|
.toolbar {
|
||||||
|
|
||||||
|
ToolbarItemGroup(placement: .topBarLeading) {
|
||||||
EditButton()
|
EditButton()
|
||||||
Button(
|
Button("Clear") {
|
||||||
action: { self.clearColors() },
|
|
||||||
label: { Text("Clear") }
|
self.clearColors()
|
||||||
)
|
}
|
||||||
},
|
}
|
||||||
trailing: HStack {
|
|
||||||
Button(
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
||||||
action: { self.changeFilter() },
|
|
||||||
label: { Text(self.filter.rawValue) }
|
Button(self.filter.rawValue) {
|
||||||
)
|
|
||||||
Button(
|
self.changeFilter()
|
||||||
action: { self.shuffleColors() },
|
}
|
||||||
label: { Text("Shuffle") }
|
Button("Shuffle") {
|
||||||
)
|
|
||||||
Button(
|
self.shuffleColors()
|
||||||
action: { self.addColor() },
|
}
|
||||||
label: { Text("Add") }
|
Button("Add") {
|
||||||
)
|
|
||||||
|
self.addColor()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -136,13 +146,10 @@ extension Modern.ColorsDemo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_ColorsDemo_MainView_Preview: PreviewProvider {
|
// MARK: - Preview
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
#Preview {
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let minimumSamples = 10
|
let minimumSamples = 10
|
||||||
try! Modern.ColorsDemo.dataStack.perform(
|
try! Modern.ColorsDemo.dataStack.perform(
|
||||||
@@ -162,16 +169,15 @@ struct _Demo_Modern_ColorsDemo_MainView_Preview: PreviewProvider {
|
|||||||
)
|
)
|
||||||
return Modern.ColorsDemo.MainView(
|
return Modern.ColorsDemo.MainView(
|
||||||
listView: { listPublisher, onPaletteTapped in
|
listView: { listPublisher, onPaletteTapped in
|
||||||
|
|
||||||
Modern.ColorsDemo.SwiftUI.ListView(
|
Modern.ColorsDemo.SwiftUI.ListView(
|
||||||
listPublisher: listPublisher,
|
listPublisher: listPublisher,
|
||||||
onPaletteTapped: onPaletteTapped
|
onPaletteTapped: onPaletteTapped
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
detailView: { objectPublisher in
|
detailView: { objectPublisher in
|
||||||
|
|
||||||
Modern.ColorsDemo.SwiftUI.DetailView(objectPublisher)
|
Modern.ColorsDemo.SwiftUI.DetailView(objectPublisher)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Combine
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -87,12 +86,17 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
if let palette = self.palette {
|
if let palette = self.palette {
|
||||||
|
|
||||||
ZStack(alignment: .center) {
|
ZStack(alignment: .center) {
|
||||||
|
|
||||||
Color(palette.$color)
|
Color(palette.$color)
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|
||||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||||
.fill(Color.white)
|
.fill(Color.white)
|
||||||
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
|
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("H: \(Int(palette.$hue * 359))°")
|
Text("H: \(Int(palette.$hue * 359))°")
|
||||||
.frame(width: 80)
|
.frame(width: 80)
|
||||||
@@ -102,6 +106,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
step: 1 / 359
|
step: 1 / 359
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("S: \(Int(palette.$saturation * 100))%")
|
Text("S: \(Int(palette.$saturation * 100))%")
|
||||||
.frame(width: 80)
|
.frame(width: 80)
|
||||||
@@ -111,6 +116,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
step: 1 / 100
|
step: 1 / 100
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("B: \(Int(palette.$brightness * 100))%")
|
Text("B: \(Int(palette.$brightness * 100))%")
|
||||||
.frame(width: 80)
|
.frame(width: 80)
|
||||||
@@ -131,30 +137,3 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_ColorsDemo_SwiftUI_DetailView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
try! Modern.ColorsDemo.dataStack.perform(
|
|
||||||
synchronous: { transaction in
|
|
||||||
|
|
||||||
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
|
||||||
palette.setRandomHue()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Modern.ColorsDemo.SwiftUI.DetailView(
|
|
||||||
Modern.ColorsDemo.palettesPublisher.snapshot.first!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
|
|
||||||
if let palette = self.palette {
|
if let palette = self.palette {
|
||||||
|
|
||||||
Color(palette.$color).overlay(
|
Color(palette.$color)
|
||||||
|
.overlay(
|
||||||
Text(palette.$colorText)
|
Text(palette.$colorText)
|
||||||
.foregroundColor(palette.$brightness > 0.6 ? .black : .white)
|
.foregroundColor(palette.$brightness > 0.6 ? .black : .white)
|
||||||
.padding(),
|
.padding(),
|
||||||
@@ -45,30 +46,3 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_ColorsDemo_SwiftUI_ItemView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
try! Modern.ColorsDemo.dataStack.perform(
|
|
||||||
synchronous: { transaction in
|
|
||||||
|
|
||||||
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
|
||||||
palette.setRandomHue()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Modern.ColorsDemo.SwiftUI.ItemView(
|
|
||||||
Modern.ColorsDemo.palettesPublisher.snapshot.first!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
|
|
||||||
ForEach(sectionIn: self.palettes) { section in
|
ForEach(sectionIn: self.palettes) { section in
|
||||||
|
|
||||||
Section(header: Text(section.sectionID)) {
|
Section(section.sectionID) {
|
||||||
|
|
||||||
ForEach(objectIn: section) { palette in
|
ForEach(objectIn: section) { palette in
|
||||||
|
|
||||||
@@ -64,8 +64,7 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .animation(.default) // breaks layout
|
// .animation(.default) // breaks layout
|
||||||
.listStyle(PlainListStyle())
|
.listStyle(.plain)
|
||||||
.edgesIgnoringSafeArea([])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -90,13 +89,10 @@ extension Modern.ColorsDemo.SwiftUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_ColorsDemo_SwiftUI_ListView_Preview: PreviewProvider {
|
// MARK: - Preview
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
#Preview {
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let minimumSamples = 10
|
let minimumSamples = 10
|
||||||
try! Modern.ColorsDemo.dataStack.perform(
|
try! Modern.ColorsDemo.dataStack.perform(
|
||||||
@@ -119,6 +115,3 @@ struct _Demo_Modern_ColorsDemo_SwiftUI_ListView_Preview: PreviewProvider {
|
|||||||
onPaletteTapped: { _ in }
|
onPaletteTapped: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Combine
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -45,30 +44,3 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
|
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_ColorsDemo_UIKit_DetailView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
try! Modern.ColorsDemo.dataStack.perform(
|
|
||||||
synchronous: { transaction in
|
|
||||||
|
|
||||||
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
|
|
||||||
palette.setRandomHue()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Modern.ColorsDemo.UIKit.DetailView(
|
|
||||||
Modern.ColorsDemo.palettesPublisher.snapshot.first!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
+9
-3
@@ -49,7 +49,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
isolated deinit {
|
||||||
|
|
||||||
self.palette.removeObserver(self)
|
self.palette.removeObserver(self)
|
||||||
}
|
}
|
||||||
@@ -88,15 +88,18 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
|
|
||||||
// MARK: ObjectObserver
|
// MARK: ObjectObserver
|
||||||
|
|
||||||
func objectMonitor(
|
nonisolated func objectMonitor(
|
||||||
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
|
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
|
||||||
didUpdateObject object: Modern.ColorsDemo.Palette,
|
didUpdateObject object: sending Modern.ColorsDemo.Palette,
|
||||||
changedPersistentKeys: Set<KeyPathString>,
|
changedPersistentKeys: Set<KeyPathString>,
|
||||||
sourceIdentifier: Any?
|
sourceIdentifier: Any?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: UIViewController
|
// MARK: UIViewController
|
||||||
@@ -245,6 +248,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
private let saturationSlider: UISlider = .init()
|
private let saturationSlider: UISlider = .init()
|
||||||
private let brightnessSlider: UISlider = .init()
|
private let brightnessSlider: UISlider = .init()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
@@ -259,6 +263,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
@@ -273,6 +278,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
@objc
|
@objc
|
||||||
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
|
||||||
|
|
||||||
|
|||||||
@@ -55,13 +55,10 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_ColorsDemo_UIKit_ListView_Preview: PreviewProvider {
|
// MARK: - Preview
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
#Preview {
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let minimumSamples = 10
|
let minimumSamples = 10
|
||||||
try! Modern.ColorsDemo.dataStack.perform(
|
try! Modern.ColorsDemo.dataStack.perform(
|
||||||
@@ -84,6 +81,3 @@ struct _Demo_Modern_ColorsDemo_UIKit_ListView_Preview: PreviewProvider {
|
|||||||
onPaletteTapped: { _ in }
|
onPaletteTapped: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ extension Modern.ColorsDemo.UIKit {
|
|||||||
/**
|
/**
|
||||||
⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ListPublisher`s safely remove deallocated observers automatically.
|
⭐️ Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ListPublisher`s safely remove deallocated observers automatically.
|
||||||
*/
|
*/
|
||||||
deinit {
|
isolated deinit {
|
||||||
|
|
||||||
self.listPublisher.removeObserver(self)
|
self.listPublisher.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ extension Modern {
|
|||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static let dataStack: DataStack = {
|
static let dataStack: DataStack = {
|
||||||
|
|
||||||
let dataStack = DataStack(
|
let dataStack = DataStack(
|
||||||
@@ -44,6 +45,7 @@ extension Modern {
|
|||||||
return dataStack
|
return dataStack
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static let palettesPublisher: ListPublisher<Modern.ColorsDemo.Palette> = Modern.ColorsDemo.dataStack.publishList(
|
static let palettesPublisher: ListPublisher<Modern.ColorsDemo.Palette> = Modern.ColorsDemo.dataStack.publishList(
|
||||||
From<Modern.ColorsDemo.Palette>()
|
From<Modern.ColorsDemo.Palette>()
|
||||||
.sectionBy(
|
.sectionBy(
|
||||||
@@ -54,6 +56,7 @@ extension Modern {
|
|||||||
.orderBy(.ascending(\.$hue))
|
.orderBy(.ascending(\.$hue))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static var filter: Modern.ColorsDemo.Filter = .all {
|
static var filter: Modern.ColorsDemo.Filter = .all {
|
||||||
|
|
||||||
didSet {
|
didSet {
|
||||||
|
|||||||
@@ -13,30 +13,34 @@ extension Modern.PlacemarksDemo {
|
|||||||
|
|
||||||
// MARK: Geocoder
|
// MARK: Geocoder
|
||||||
|
|
||||||
|
@MainActor
|
||||||
final class Geocoder {
|
final class Geocoder {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
func geocode(
|
func geocode(
|
||||||
place: ObjectSnapshot<Modern.PlacemarksDemo.Place>,
|
place: ObjectSnapshot<Modern.PlacemarksDemo.Place>
|
||||||
completion: @escaping (_ title: String?, _ subtitle: String?) -> Void
|
) async -> (title: String?, subtitle: String?) {
|
||||||
) {
|
|
||||||
|
|
||||||
self.geocoder?.cancelGeocode()
|
self.geocoder?.cancelGeocode()
|
||||||
|
|
||||||
let geocoder = CLGeocoder()
|
let geocoder = CLGeocoder()
|
||||||
self.geocoder = geocoder
|
self.geocoder = geocoder
|
||||||
geocoder.reverseGeocodeLocation(
|
|
||||||
CLLocation(latitude: place.$latitude, longitude: place.$longitude),
|
|
||||||
completionHandler: { (placemarks, error) -> Void in
|
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
|
|
||||||
|
if self.geocoder === geocoder {
|
||||||
self.geocoder = nil
|
self.geocoder = nil
|
||||||
}
|
}
|
||||||
guard let placemark = placemarks?.first else {
|
}
|
||||||
|
|
||||||
return
|
do {
|
||||||
|
let placemarks = try await geocoder.reverseGeocodeLocation(
|
||||||
|
CLLocation(latitude: place.$latitude, longitude: place.$longitude)
|
||||||
|
)
|
||||||
|
guard let placemark = placemarks.first else {
|
||||||
|
|
||||||
|
return (nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = CNMutablePostalAddress()
|
let address = CNMutablePostalAddress()
|
||||||
@@ -49,7 +53,7 @@ extension Modern.PlacemarksDemo {
|
|||||||
address.country = placemark.country ?? ""
|
address.country = placemark.country ?? ""
|
||||||
address.isoCountryCode = placemark.isoCountryCode ?? ""
|
address.isoCountryCode = placemark.isoCountryCode ?? ""
|
||||||
|
|
||||||
completion(
|
return (
|
||||||
placemark.name,
|
placemark.name,
|
||||||
CNPostalAddressFormatter.string(
|
CNPostalAddressFormatter.string(
|
||||||
from: address,
|
from: address,
|
||||||
@@ -57,7 +61,10 @@ extension Modern.PlacemarksDemo {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
catch {
|
||||||
|
|
||||||
|
return (nil, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Combine
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import Foundation
|
import Foundation
|
||||||
import MapKit
|
import MapKit
|
||||||
@@ -74,35 +73,6 @@ extension Modern.PlacemarksDemo {
|
|||||||
@ObjectState(Modern.PlacemarksDemo.placePublisher)
|
@ObjectState(Modern.PlacemarksDemo.placePublisher)
|
||||||
var place: ObjectSnapshot<Modern.PlacemarksDemo.Place>?
|
var place: ObjectSnapshot<Modern.PlacemarksDemo.Place>?
|
||||||
|
|
||||||
init() {
|
|
||||||
|
|
||||||
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 else {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.demoUnsafeTransaction(
|
|
||||||
title: title,
|
|
||||||
subtitle: subtitle,
|
|
||||||
for: snapshot
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
@@ -130,34 +100,41 @@ extension Modern.PlacemarksDemo {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Placemarks")
|
.task(id: self.place.map({ "\($0.$latitude),\($0.$longitude)" })) {
|
||||||
.navigationBarItems(
|
|
||||||
trailing: Button("Random") {
|
guard let place = self.place else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let geocoded = await self.geocoder.geocode(place: place)
|
||||||
|
guard self.place?.objectID() == place.objectID() else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard geocoded.title != nil || geocoded.subtitle != nil else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.demoUnsafeTransaction(
|
||||||
|
title: geocoded.title,
|
||||||
|
subtitle: geocoded.subtitle,
|
||||||
|
for: place
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.navigationTitle("Placemarks")
|
||||||
|
.toolbar {
|
||||||
|
|
||||||
|
Button("Random") {
|
||||||
|
|
||||||
self.demoSynchronousTransaction()
|
self.demoSynchronousTransaction()
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private var sinkCancellable: AnyCancellable?
|
@State
|
||||||
private let geocoder = Modern.PlacemarksDemo.Geocoder()
|
private var geocoder = Modern.PlacemarksDemo.Geocoder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_PlacemarksDemo_MainView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
Modern.PlacemarksDemo.MainView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ extension Modern {
|
|||||||
/**
|
/**
|
||||||
⭐️ Sample 1: Setting up the `DataStack` and storage
|
⭐️ Sample 1: Setting up the `DataStack` and storage
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
static let dataStack: DataStack = {
|
static let dataStack: DataStack = {
|
||||||
|
|
||||||
let dataStack = DataStack(
|
let dataStack = DataStack(
|
||||||
@@ -46,6 +47,7 @@ extension Modern {
|
|||||||
return dataStack
|
return dataStack
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static let placePublisher: ObjectPublisher<Modern.PlacemarksDemo.Place> = {
|
static let placePublisher: ObjectPublisher<Modern.PlacemarksDemo.Place> = {
|
||||||
|
|
||||||
let dataStack = Modern.PlacemarksDemo.dataStack
|
let dataStack = Modern.PlacemarksDemo.dataStack
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Combine
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -31,7 +30,7 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
self.listView()
|
self.listView()
|
||||||
.frame(minHeight: 0, maxHeight: .infinity)
|
.frame(minHeight: 0, maxHeight: .infinity)
|
||||||
.edgesIgnoringSafeArea(.vertical)
|
.ignoresSafeArea(.container, edges: .vertical)
|
||||||
|
|
||||||
if self.pokedexEntries.isEmpty {
|
if self.pokedexEntries.isEmpty {
|
||||||
|
|
||||||
@@ -56,7 +55,7 @@ extension Modern.PokedexDemo {
|
|||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle("Pokedex")
|
.navigationTitle("Pokedex")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -69,27 +68,9 @@ extension Modern.PokedexDemo {
|
|||||||
)
|
)
|
||||||
private var pokedexEntries
|
private var pokedexEntries
|
||||||
|
|
||||||
@ObservedObject
|
@State
|
||||||
private var service: Modern.PokedexDemo.Service = .init()
|
private var service = Modern.PokedexDemo.Service()
|
||||||
|
|
||||||
private let listView: () -> ListView
|
private let listView: () -> ListView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
@available(iOS 14.0, *)
|
|
||||||
struct _Demo_Modern_PokedexDemo_MainView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
Modern.PokedexDemo.MainView(
|
|
||||||
listView: Modern.PokedexDemo.UIKit.ListView.init
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import CoreData
|
||||||
import Combine
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import UIKit
|
import Foundation
|
||||||
|
import Observation
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Modern.PokedexDemo
|
// MARK: - Modern.PokedexDemo
|
||||||
@@ -14,52 +14,36 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
// MARK: - Modern.PokedexDemo.Service
|
// MARK: - Modern.PokedexDemo.Service
|
||||||
|
|
||||||
final class Service: ObservableObject {
|
@MainActor
|
||||||
|
@Observable
|
||||||
|
final class Service {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
⭐️ Sample 1: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are tuples
|
⭐️ Sample 1: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are tuples
|
||||||
*/
|
*/
|
||||||
private static func importPokedexEntries(
|
private static func importPokedexEntries(from data: Data) async throws {
|
||||||
from output: URLSession.DataTaskPublisher.Output
|
|
||||||
) -> Future<Void, Modern.PokedexDemo.Service.Error> {
|
|
||||||
|
|
||||||
return .init { promise in
|
do {
|
||||||
|
|
||||||
Modern.PokedexDemo.dataStack.perform(
|
try await Modern.PokedexDemo.dataStack.async.perform { transaction -> Void in
|
||||||
asynchronous: { transaction -> Void in
|
|
||||||
|
|
||||||
let json: Dictionary<String, Any> = try self.parseJSON(
|
let json: Dictionary<String, Any> = try self.parseJSON(
|
||||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
try JSONSerialization.jsonObject(with: data, options: [])
|
||||||
)
|
)
|
||||||
let results: [Dictionary<String, Any>] = try self.parseJSON(
|
let results: [Dictionary<String, Any>] = try self.parseJSON(
|
||||||
json["results"]
|
json["results"]
|
||||||
)
|
)
|
||||||
_ = try transaction.importUniqueObjects(
|
_ = try transaction.importUniqueObjects(
|
||||||
Into<Modern.PokedexDemo.PokedexEntry>(),
|
Into<Modern.PokedexDemo.PokedexEntry>(),
|
||||||
sourceArray: results.enumerated().map { (index, json) in
|
sourceArray: results.enumerated().map { index, json in
|
||||||
(index: index, json: json)
|
(index: index, json: json)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
success: { result in
|
|
||||||
|
|
||||||
promise(.success(result))
|
|
||||||
},
|
|
||||||
failure: { error in
|
|
||||||
|
|
||||||
switch error {
|
|
||||||
|
|
||||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
|
||||||
promise(.failure(error))
|
|
||||||
|
|
||||||
case .userError(let error):
|
|
||||||
promise(.failure(.otherError(error)))
|
|
||||||
|
|
||||||
case let error:
|
|
||||||
promise(.failure(.saveError(error)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
catch {
|
||||||
|
|
||||||
|
throw self.mapError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,17 +51,14 @@ extension Modern.PokedexDemo {
|
|||||||
⭐️ Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
|
⭐️ Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
|
||||||
*/
|
*/
|
||||||
private static func importSpecies(
|
private static func importSpecies(
|
||||||
for details: ObjectSnapshot<Modern.PokedexDemo.Details>,
|
for detailsObjectID: NSManagedObjectID,
|
||||||
from output: URLSession.DataTaskPublisher.Output
|
from data: Data
|
||||||
) -> Future<ObjectSnapshot<Modern.PokedexDemo.Species>, Modern.PokedexDemo.Service.Error> {
|
) async throws -> ObjectSnapshot<Modern.PokedexDemo.Species> {
|
||||||
|
|
||||||
return .init { promise in
|
let speciesObjectID = try await Modern.PokedexDemo.dataStack.async.perform { transaction -> NSManagedObjectID in
|
||||||
|
|
||||||
Modern.PokedexDemo.dataStack.perform(
|
|
||||||
asynchronous: { transaction -> Modern.PokedexDemo.Species in
|
|
||||||
|
|
||||||
let json: Dictionary<String, Any> = try self.parseJSON(
|
let json: Dictionary<String, Any> = try self.parseJSON(
|
||||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
try JSONSerialization.jsonObject(with: data, options: [])
|
||||||
)
|
)
|
||||||
guard
|
guard
|
||||||
let species = try transaction.importUniqueObject(
|
let species = try transaction.importUniqueObject(
|
||||||
@@ -88,103 +69,65 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||||
}
|
}
|
||||||
details.asEditable(in: transaction)?.species = species
|
transaction
|
||||||
return species
|
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
|
||||||
},
|
.species = species
|
||||||
success: { species in
|
return species.objectID()
|
||||||
|
|
||||||
promise(.success(species.asSnapshot(in: Modern.PokedexDemo.dataStack)!))
|
|
||||||
},
|
|
||||||
failure: { error in
|
|
||||||
|
|
||||||
switch error {
|
|
||||||
|
|
||||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
|
||||||
promise(.failure(error))
|
|
||||||
|
|
||||||
case .userError(let error):
|
|
||||||
promise(.failure(.otherError(error)))
|
|
||||||
|
|
||||||
case let error:
|
|
||||||
promise(.failure(.saveError(error)))
|
|
||||||
}
|
}
|
||||||
|
guard
|
||||||
|
let species: Modern.PokedexDemo.Species = Modern.PokedexDemo.dataStack.fetchExisting(speciesObjectID),
|
||||||
|
let snapshot = species.asSnapshot()
|
||||||
|
else {
|
||||||
|
|
||||||
|
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||||
}
|
}
|
||||||
)
|
return snapshot
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
⭐️ Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
|
⭐️ Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
|
||||||
*/
|
*/
|
||||||
private static func importForms(
|
private static func importForms(
|
||||||
for details: ObjectSnapshot<Modern.PokedexDemo.Details>,
|
for detailsObjectID: NSManagedObjectID,
|
||||||
from outputs: [URLSession.DataTaskPublisher.Output]
|
from dataArray: [Data]
|
||||||
) -> Future<Void, Modern.PokedexDemo.Service.Error> {
|
) async throws {
|
||||||
|
|
||||||
return .init { promise in
|
do {
|
||||||
|
|
||||||
Modern.PokedexDemo.dataStack.perform(
|
try await Modern.PokedexDemo.dataStack.async.perform { transaction -> Void in
|
||||||
asynchronous: { transaction -> Void in
|
|
||||||
|
|
||||||
let forms = try transaction.importUniqueObjects(
|
let forms = try transaction.importUniqueObjects(
|
||||||
Into<Modern.PokedexDemo.Form>(),
|
Into<Modern.PokedexDemo.Form>(),
|
||||||
sourceArray: outputs.map { output in
|
sourceArray: dataArray.map { data in
|
||||||
|
|
||||||
return try self.parseJSON(
|
try self.parseJSON(
|
||||||
try JSONSerialization.jsonObject(with: output.data, options: [])
|
try JSONSerialization.jsonObject(with: data, options: [])
|
||||||
)
|
) as [String: Any]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
guard !forms.isEmpty else {
|
guard !forms.isEmpty else {
|
||||||
|
|
||||||
throw Modern.PokedexDemo.Service.Error.unexpected
|
throw Modern.PokedexDemo.Service.Error.unexpected
|
||||||
}
|
}
|
||||||
details.asEditable(in: transaction)?.forms = forms
|
transaction
|
||||||
},
|
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
|
||||||
success: {
|
.forms = forms
|
||||||
|
|
||||||
promise(.success(()))
|
|
||||||
},
|
|
||||||
failure: { error in
|
|
||||||
|
|
||||||
switch error {
|
|
||||||
|
|
||||||
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
|
||||||
promise(.failure(error))
|
|
||||||
|
|
||||||
case .userError(let error):
|
|
||||||
promise(.failure(.otherError(error)))
|
|
||||||
|
|
||||||
case let error:
|
|
||||||
promise(.failure(.saveError(error)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
catch {
|
||||||
|
|
||||||
|
throw self.mapError(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
private(set) var isLoading: Bool = false {
|
private(set) var isLoading: Bool = false
|
||||||
|
|
||||||
willSet {
|
|
||||||
|
|
||||||
self.objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private(set) var lastError: (error: Modern.PokedexDemo.Service.Error, retry: () -> Void)? {
|
|
||||||
|
|
||||||
willSet {
|
|
||||||
|
|
||||||
self.objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
static func parseJSON<Output>(
|
static nonisolated func parseJSON<Output>(
|
||||||
_ json: Any?,
|
_ json: Any?,
|
||||||
file: StaticString = #file,
|
file: StaticString = #file,
|
||||||
line: Int = #line
|
line: Int = #line
|
||||||
@@ -204,7 +147,7 @@ extension Modern.PokedexDemo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func parseJSON<JSONType, Output>(
|
static nonisolated func parseJSON<JSONType, Output>(
|
||||||
_ json: Any?,
|
_ json: Any?,
|
||||||
transformer: (JSONType) throws -> Output?,
|
transformer: (JSONType) throws -> Output?,
|
||||||
file: StaticString = #file,
|
file: StaticString = #file,
|
||||||
@@ -236,143 +179,225 @@ extension Modern.PokedexDemo {
|
|||||||
|
|
||||||
func fetchPokedexEntries() {
|
func fetchPokedexEntries() {
|
||||||
|
|
||||||
self.cancellable["pokedexEntries"] = self.pokedexEntries
|
self.pokedexEntriesTask?.cancel()
|
||||||
.receive(on: DispatchQueue.main)
|
self.pokedexEntriesTask = Task { [weak self] in
|
||||||
.handleEvents(
|
|
||||||
receiveSubscription: { [weak self] _ in
|
|
||||||
|
|
||||||
guard let self = self else {
|
await self?.runFetchPokedexEntries()
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
self.lastError = nil
|
|
||||||
self.isLoading = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.sink(
|
|
||||||
receiveCompletion: { [weak self] completion in
|
|
||||||
|
|
||||||
guard let self = self else {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.isLoading = false
|
|
||||||
switch completion {
|
|
||||||
|
|
||||||
case .finished:
|
|
||||||
self.lastError = nil
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
self.lastError = (
|
|
||||||
error: error,
|
|
||||||
retry: { [weak self] in
|
|
||||||
|
|
||||||
self?.fetchPokedexEntries()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
receiveValue: {}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchDetails(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
func fetchDetails(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
||||||
|
|
||||||
self.fetchSpeciesIfNeeded(for: pokedexEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
private var cancellable: Dictionary<String, AnyCancellable> = [:]
|
|
||||||
|
|
||||||
private lazy var pokedexEntries: AnyPublisher<Void, Modern.PokedexDemo.Service.Error> = URLSession.shared
|
|
||||||
.dataTaskPublisher(
|
|
||||||
for: URL(string: "https://pokeapi.co/api/v2/pokemon?limit=10000&offset=0")!
|
|
||||||
)
|
|
||||||
.mapError({ .networkError($0) })
|
|
||||||
.flatMap(Self.importPokedexEntries(from:))
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
|
|
||||||
private func fetchSpeciesIfNeeded(for pokedexEntry: ObjectSnapshot<Modern.PokedexDemo.PokedexEntry>) {
|
|
||||||
|
|
||||||
guard let details = pokedexEntry.$details?.snapshot else {
|
guard let details = pokedexEntry.$details?.snapshot else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let species = details.$species?.snapshot {
|
if let species = details.$species?.snapshot {
|
||||||
|
|
||||||
self.fetchFormsIfNeeded(for: species)
|
self.fetchFormsIfNeeded(
|
||||||
|
key: species.$id,
|
||||||
|
detailsObjectID: details.objectID(),
|
||||||
|
species: species
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.cancellable["species.\(pokedexEntry.$id)"] = URLSession.shared
|
let key = pokedexEntry.$id
|
||||||
.dataTaskPublisher(for: pokedexEntry.$speciesURL)
|
guard self.detailTasks[key] == nil else {
|
||||||
.mapError({ .networkError($0) })
|
|
||||||
.flatMap({ Self.importSpecies(for: details, from: $0) })
|
|
||||||
.sink(
|
|
||||||
receiveCompletion: { completion in
|
|
||||||
|
|
||||||
switch completion {
|
|
||||||
|
|
||||||
case .finished:
|
|
||||||
break
|
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
receiveValue: { species in
|
|
||||||
|
|
||||||
self.fetchFormsIfNeeded(for: species)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetchFormsIfNeeded(for species: ObjectSnapshot<Modern.PokedexDemo.Species>) {
|
|
||||||
|
|
||||||
guard
|
|
||||||
let details = species.$details?.snapshot,
|
|
||||||
details.$forms.isEmpty
|
|
||||||
else {
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.cancellable["forms.\(species.$id)"] = species
|
let speciesURL = pokedexEntry.$speciesURL
|
||||||
.$formsURLs
|
let detailsObjectID = details.objectID()
|
||||||
.map(
|
self.detailTasks[key] = Task { [weak self] in
|
||||||
{
|
|
||||||
URLSession.shared
|
guard let self else {
|
||||||
.dataTaskPublisher(for: $0)
|
|
||||||
.mapError({ Modern.PokedexDemo.Service.Error.networkError($0) })
|
return
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
}
|
||||||
)
|
defer {
|
||||||
.reduce(
|
|
||||||
into: Just<[URLSession.DataTaskPublisher.Output]>([])
|
self.detailTasks.removeValue(forKey: key)
|
||||||
.setFailureType(to: Modern.PokedexDemo.Service.Error.self)
|
|
||||||
.eraseToAnyPublisher(),
|
|
||||||
{ (result, publisher) in
|
|
||||||
result = result
|
|
||||||
.zip(publisher, { $0 + [$1] })
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
}
|
}
|
||||||
|
await self.fetchSpecies(
|
||||||
|
key: key,
|
||||||
|
detailsObjectID: detailsObjectID,
|
||||||
|
speciesURL: speciesURL
|
||||||
)
|
)
|
||||||
.flatMap({ Self.importForms(for: details, from: $0) })
|
}
|
||||||
.sink(
|
}
|
||||||
receiveCompletion: { completion in
|
|
||||||
|
|
||||||
switch completion {
|
|
||||||
|
|
||||||
case .finished:
|
// MARK: Private
|
||||||
break
|
|
||||||
|
@ObservationIgnored
|
||||||
|
private static let pokedexURL = URL(
|
||||||
|
string: "https://pokeapi.co/api/v2/pokemon?limit=10000&offset=0"
|
||||||
|
)!
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
|
private var pokedexEntriesTask: Task<Void, Never>?
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
|
private var detailTasks: [String: Task<Void, Never>] = [:]
|
||||||
|
|
||||||
|
private static func mapError(_ error: CoreStoreError) -> Modern.PokedexDemo.Service.Error {
|
||||||
|
|
||||||
|
switch error {
|
||||||
|
case .userError(let error as Modern.PokedexDemo.Service.Error):
|
||||||
|
return error
|
||||||
|
|
||||||
|
case .userError(let error):
|
||||||
|
return .otherError(error)
|
||||||
|
|
||||||
|
case let error:
|
||||||
|
return .saveError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func runFetchPokedexEntries() async {
|
||||||
|
|
||||||
|
self.isLoading = true
|
||||||
|
defer {
|
||||||
|
|
||||||
|
self.isLoading = false
|
||||||
|
self.pokedexEntriesTask = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
let (data, _) = try await URLSession.shared.data(from: Self.pokedexURL)
|
||||||
|
try Task.checkCancellation()
|
||||||
|
try await Self.importPokedexEntries(from: data)
|
||||||
|
}
|
||||||
|
catch is CancellationError {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch let error as Modern.PokedexDemo.Service.Error {
|
||||||
|
|
||||||
case .failure(let error):
|
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
},
|
catch let error as URLError {
|
||||||
receiveValue: { _ in }
|
|
||||||
|
print(Modern.PokedexDemo.Service.Error.networkError(error))
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
print(Modern.PokedexDemo.Service.Error.otherError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchSpecies(
|
||||||
|
key: String,
|
||||||
|
detailsObjectID: NSManagedObjectID,
|
||||||
|
speciesURL: URL
|
||||||
|
) async {
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
let (data, _) = try await URLSession.shared.data(from: speciesURL)
|
||||||
|
try Task.checkCancellation()
|
||||||
|
|
||||||
|
let species = try await Self.importSpecies(
|
||||||
|
for: detailsObjectID,
|
||||||
|
from: data
|
||||||
)
|
)
|
||||||
|
guard species.$details?.snapshot?.$forms.isEmpty == true else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await self.fetchForms(
|
||||||
|
detailsObjectID: detailsObjectID,
|
||||||
|
formsURLs: species.$formsURLs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch is CancellationError {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch let error as Modern.PokedexDemo.Service.Error {
|
||||||
|
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
catch let error as URLError {
|
||||||
|
|
||||||
|
print(Modern.PokedexDemo.Service.Error.networkError(error))
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
print(Modern.PokedexDemo.Service.Error.otherError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchFormsIfNeeded(
|
||||||
|
key: String,
|
||||||
|
detailsObjectID: NSManagedObjectID,
|
||||||
|
species: ObjectSnapshot<Modern.PokedexDemo.Species>
|
||||||
|
) {
|
||||||
|
|
||||||
|
guard species.$details?.snapshot?.$forms.isEmpty == true else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard self.detailTasks[key] == nil else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let formsURLs = species.$formsURLs
|
||||||
|
self.detailTasks[key] = Task { [weak self] in
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
|
||||||
|
self.detailTasks.removeValue(forKey: key)
|
||||||
|
}
|
||||||
|
await self.fetchForms(
|
||||||
|
detailsObjectID: detailsObjectID,
|
||||||
|
formsURLs: formsURLs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchForms(
|
||||||
|
detailsObjectID: NSManagedObjectID,
|
||||||
|
formsURLs: [URL]
|
||||||
|
) async {
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
var dataArray: [Data] = []
|
||||||
|
dataArray.reserveCapacity(formsURLs.count)
|
||||||
|
|
||||||
|
for url in formsURLs {
|
||||||
|
let (data, _) = try await URLSession.shared.data(from: url)
|
||||||
|
try Task.checkCancellation()
|
||||||
|
dataArray.append(data)
|
||||||
|
}
|
||||||
|
try await Self.importForms(
|
||||||
|
for: detailsObjectID,
|
||||||
|
from: dataArray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch is CancellationError {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch let error as Modern.PokedexDemo.Service.Error {
|
||||||
|
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
catch let error as URLError {
|
||||||
|
|
||||||
|
print(Modern.PokedexDemo.Service.Error.networkError(error))
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
print(Modern.PokedexDemo.Service.Error.otherError(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ extension Modern.PokedexDemo.UIKit {
|
|||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self._service = State(initialValue: Modern.PokedexDemo.Service())
|
||||||
self.service = Modern.PokedexDemo.Service.init()
|
|
||||||
self.listPublisher = Modern.PokedexDemo.dataStack
|
self.listPublisher = Modern.PokedexDemo.dataStack
|
||||||
.publishList(
|
.publishList(
|
||||||
From<Modern.PokedexDemo.PokedexEntry>()
|
From<Modern.PokedexDemo.PokedexEntry>()
|
||||||
@@ -45,26 +44,16 @@ extension Modern.PokedexDemo.UIKit {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
@ObservedObject
|
@State
|
||||||
private var service: Modern.PokedexDemo.Service
|
private var service: Modern.PokedexDemo.Service
|
||||||
|
|
||||||
private let listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
private let listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_PokedexDemo_UIKit_ListView_Preview: PreviewProvider {
|
// MARK: - Preview
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
#Preview {
|
||||||
|
Modern.PokedexDemo.UIKit.ListView()
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
let service = Modern.PokedexDemo.Service()
|
|
||||||
service.fetchPokedexEntries()
|
|
||||||
|
|
||||||
return Modern.PokedexDemo.UIKit.ListView()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ extension Modern {
|
|||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static let dataStack: DataStack = {
|
static let dataStack: DataStack = {
|
||||||
|
|
||||||
let dataStack = DataStack(
|
let dataStack = DataStack(
|
||||||
@@ -49,6 +50,7 @@ extension Modern {
|
|||||||
return dataStack
|
return dataStack
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
|
static let pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry> = Modern.PokedexDemo.dataStack.publishList(
|
||||||
From<Modern.PokedexDemo.PokedexEntry>()
|
From<Modern.PokedexDemo.PokedexEntry>()
|
||||||
.orderBy(.ascending(\.$index))
|
.orderBy(.ascending(\.$index))
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ extension Modern.TimeZonesDemo {
|
|||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
init(title: String, subtitle: String) {
|
init(title: String, subtitle: String) {
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
}
|
}
|
||||||
@@ -23,10 +24,13 @@ extension Modern.TimeZonesDemo {
|
|||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
Text(self.title)
|
Text(self.title)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
Text(self.subtitle)
|
Text(self.subtitle)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@@ -40,19 +44,3 @@ extension Modern.TimeZonesDemo {
|
|||||||
fileprivate let subtitle: String
|
fileprivate let subtitle: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_TimeZonesDemo_ItemView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
Modern.TimeZonesDemo.ItemView(
|
|
||||||
title: "Item Title",
|
|
||||||
subtitle: "A subtitle caption for this item"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -53,15 +53,18 @@ extension Modern.TimeZonesDemo {
|
|||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
List {
|
List {
|
||||||
|
|
||||||
ForEach(self.values, id: \.title) { item in
|
ForEach(self.values, id: \.title) { item in
|
||||||
|
|
||||||
Modern.TimeZonesDemo.ItemView(
|
Modern.TimeZonesDemo.ItemView(
|
||||||
title: item.title,
|
title: item.title,
|
||||||
subtitle: item.subtitle
|
subtitle: item.subtitle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle(self.title)
|
.navigationTitle(self.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -71,24 +74,3 @@ extension Modern.TimeZonesDemo {
|
|||||||
private let values: [(title: String, subtitle: String)]
|
private let values: [(title: String, subtitle: String)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_TimeZonesDemo_ListView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
Modern.TimeZonesDemo.ListView(
|
|
||||||
title: "Title",
|
|
||||||
objects: try! Modern.TimeZonesDemo.dataStack.fetchAll(
|
|
||||||
From<Modern.TimeZonesDemo.TimeZone>()
|
|
||||||
.orderBy(.ascending(\.$name))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -137,36 +137,54 @@ extension Modern.TimeZonesDemo {
|
|||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
||||||
List {
|
List {
|
||||||
Section(header: Text("Fetching objects")) {
|
|
||||||
|
Section("Fetching objects") {
|
||||||
|
|
||||||
ForEach(self.fetchingItems, id: \.title) { item in
|
ForEach(self.fetchingItems, id: \.title) { item in
|
||||||
Menu.ItemView(
|
|
||||||
title: item.title,
|
NavigationLink(
|
||||||
destination: {
|
destination: {
|
||||||
|
|
||||||
Modern.TimeZonesDemo.ListView(
|
Modern.TimeZonesDemo.ListView(
|
||||||
title: item.title,
|
title: item.title,
|
||||||
objects: item.objects()
|
objects: item.objects()
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
|
||||||
|
Menu.ItemView(
|
||||||
|
title: item.title
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section(header: Text("Querying raw values")) {
|
Section("Querying raw values") {
|
||||||
|
|
||||||
ForEach(self.queryingItems, id: \.title) { item in
|
ForEach(self.queryingItems, id: \.title) { item in
|
||||||
Menu.ItemView(
|
|
||||||
title: item.title,
|
NavigationLink(
|
||||||
destination: {
|
destination: {
|
||||||
|
|
||||||
Modern.TimeZonesDemo.ListView(
|
Modern.TimeZonesDemo.ListView(
|
||||||
title: item.title,
|
title: item.title,
|
||||||
value: item.value()
|
value: item.value()
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
|
||||||
|
Menu.ItemView(
|
||||||
|
title: item.title
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(GroupedListStyle())
|
.listStyle(.grouped)
|
||||||
.navigationBarTitle("Time Zones")
|
.navigationTitle("Time Zones")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -217,18 +235,3 @@ extension Modern.TimeZonesDemo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Modern_TimeZonesDemo_MainView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
Modern.TimeZonesDemo.MainView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
|
||||||
|
|
||||||
// MARK: - ImageDownloader
|
// MARK: - ImageDownloader
|
||||||
|
|
||||||
final class ImageDownloader: ObservableObject {
|
@MainActor
|
||||||
|
final class ImageDownloader {
|
||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: Internal
|
||||||
|
|
||||||
private(set) var image: UIImage?
|
private(set) var image: UIImage?
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ final class ImageDownloader: ObservableObject {
|
|||||||
init(url: URL?) {
|
init(url: URL?) {
|
||||||
|
|
||||||
self.url = url
|
self.url = url
|
||||||
guard let url = url else {
|
guard let url else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -31,33 +31,42 @@ final class ImageDownloader: ObservableObject {
|
|||||||
|
|
||||||
func fetchImage(completion: @escaping (UIImage) -> Void = { _ in }) {
|
func fetchImage(completion: @escaping (UIImage) -> Void = { _ in }) {
|
||||||
|
|
||||||
guard let url = url else {
|
guard let url else {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let image = Self.cache[url] {
|
if let image = Self.cache[url] {
|
||||||
|
|
||||||
self.objectWillChange.send()
|
|
||||||
self.image = image
|
self.image = image
|
||||||
completion(image)
|
completion(image)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.cancellable = URLSession.shared
|
|
||||||
.dataTaskPublisher(for: url)
|
|
||||||
.receive(on: DispatchQueue.main)
|
|
||||||
.sink(
|
|
||||||
receiveCompletion: { _ in },
|
|
||||||
receiveValue: { output in
|
|
||||||
|
|
||||||
if let image = UIImage(data: output.data) {
|
self.task = Task { [weak self] in
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
|
||||||
|
let (data, _) = try await URLSession.shared.data(from: url)
|
||||||
|
guard
|
||||||
|
!Task.isCancelled,
|
||||||
|
let image = UIImage(data: data)
|
||||||
|
else {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
Self.cache[url] = image
|
Self.cache[url] = image
|
||||||
self.objectWillChange.send()
|
|
||||||
self.image = image
|
self.image = image
|
||||||
completion(image)
|
completion(image)
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -65,5 +74,5 @@ final class ImageDownloader: ObservableObject {
|
|||||||
|
|
||||||
private static var cache: [URL: UIImage] = [:]
|
private static var cache: [URL: UIImage] = [:]
|
||||||
|
|
||||||
private var cancellable: AnyCancellable?
|
private var task: Task<Void, Never>?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
//
|
|
||||||
// Demo
|
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
// MARK: - LazyView
|
|
||||||
|
|
||||||
struct LazyView<Content: View>: View {
|
|
||||||
|
|
||||||
// MARK: Internal
|
|
||||||
|
|
||||||
init(_ load: @escaping () -> Content) {
|
|
||||||
|
|
||||||
self.load = load
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: View
|
|
||||||
|
|
||||||
var body: Content {
|
|
||||||
|
|
||||||
self.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
private let load: () -> Content
|
|
||||||
}
|
|
||||||
@@ -10,34 +10,36 @@ extension Menu {
|
|||||||
|
|
||||||
// MARK: - Menu.ItemView
|
// MARK: - Menu.ItemView
|
||||||
|
|
||||||
struct ItemView<Destination: View>: View {
|
struct ItemView: View {
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
init(
|
init(
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String? = nil,
|
subtitle: String? = nil,
|
||||||
destination: @escaping () -> Destination
|
isEnabled: Bool = true
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.title = title
|
self.title = title
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
self.destination = destination
|
self.isEnabled = isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationLink(destination: LazyView(self.destination)) {
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
|
|
||||||
Text(self.title)
|
Text(self.title)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.primary)
|
.foregroundStyle(self.isEnabled ? .primary : .secondary)
|
||||||
|
|
||||||
self.subtitle.map {
|
self.subtitle.map {
|
||||||
|
|
||||||
Text($0)
|
Text($0)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,25 +49,6 @@ extension Menu {
|
|||||||
|
|
||||||
fileprivate let title: String
|
fileprivate let title: String
|
||||||
fileprivate let subtitle: String?
|
fileprivate let subtitle: String?
|
||||||
fileprivate let destination: () -> Destination
|
fileprivate let isEnabled: Bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Menu_ItemView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Item Title",
|
|
||||||
subtitle: "A subtitle caption for this item",
|
|
||||||
destination: {
|
|
||||||
Color.blue
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -14,121 +14,51 @@ extension Menu {
|
|||||||
|
|
||||||
struct MainView: View {
|
struct MainView: View {
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var selection: Menu.Route?
|
||||||
|
|
||||||
|
|
||||||
// MARK: View
|
// MARK: View
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
|
||||||
List {
|
NavigationSplitView(
|
||||||
Section(header: Text("Modern (CoreStoreObject subclasses)")) {
|
sidebar: {
|
||||||
|
|
||||||
|
List(selection: self.$selection) {
|
||||||
|
|
||||||
|
ForEach(Menu.Section.allCases, id: \.self) { section in
|
||||||
|
|
||||||
|
Section(section.rawValue) {
|
||||||
|
|
||||||
|
ForEach(section.routes) { route in
|
||||||
|
|
||||||
Menu.ItemView(
|
Menu.ItemView(
|
||||||
title: "Placemarks",
|
title: route.title,
|
||||||
subtitle: "Making changes using Transactions",
|
subtitle: route.subtitle,
|
||||||
destination: {
|
isEnabled: route.isEnabled
|
||||||
Modern.PlacemarksDemo.MainView()
|
)
|
||||||
|
.tag(route as Menu.Route?)
|
||||||
|
.disabled(!route.isEnabled)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Time Zones",
|
|
||||||
subtitle: "Fetching objects and Querying raw values",
|
|
||||||
destination: {
|
|
||||||
Modern.TimeZonesDemo.MainView()
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
Menu.ItemView(
|
}
|
||||||
title: "Colors (UIKit)",
|
.navigationTitle("CoreStore Demos")
|
||||||
subtitle: "Observing list changes and single-object changes using DiffableDataSources",
|
.listStyle(.sidebar)
|
||||||
destination: {
|
|
||||||
Modern.ColorsDemo.MainView(
|
|
||||||
listView: { listPublisher, onPaletteTapped in
|
|
||||||
Modern.ColorsDemo.UIKit.ListView(
|
|
||||||
listPublisher: listPublisher,
|
|
||||||
onPaletteTapped: onPaletteTapped
|
|
||||||
)
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
},
|
},
|
||||||
detailView: { objectPublisher in
|
detail: {
|
||||||
Modern.ColorsDemo.UIKit.DetailView(objectPublisher)
|
|
||||||
|
if let selection = self.selection {
|
||||||
|
|
||||||
|
selection.destination
|
||||||
}
|
}
|
||||||
)
|
else {
|
||||||
}
|
|
||||||
)
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Colors (SwiftUI)",
|
|
||||||
subtitle: "Observing list changes and single-object changes using SwiftUI bindings",
|
|
||||||
destination: {
|
|
||||||
Modern.ColorsDemo.MainView(
|
|
||||||
listView: { listPublisher, onPaletteTapped in
|
|
||||||
Modern.ColorsDemo.SwiftUI.ListView(
|
|
||||||
listPublisher: listPublisher,
|
|
||||||
onPaletteTapped: onPaletteTapped
|
|
||||||
)
|
|
||||||
},
|
|
||||||
detailView: { objectPublisher in
|
|
||||||
Modern.ColorsDemo.SwiftUI.DetailView(objectPublisher)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Pokedex API",
|
|
||||||
subtitle: "Importing JSON data from external source",
|
|
||||||
destination: {
|
|
||||||
Modern.PokedexDemo.MainView(
|
|
||||||
listView: Modern.PokedexDemo.UIKit.ListView.init
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Section(header: Text("Classic (NSManagedObject subclasses)")) {
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Colors",
|
|
||||||
subtitle: "Observing list changes and single-object changes using ListMonitor",
|
|
||||||
destination: {
|
|
||||||
Classic.ColorsDemo.MainView()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Section(header: Text("Advanced")) {
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Accounts",
|
|
||||||
subtitle: "Switching between multiple persistent stores",
|
|
||||||
destination: { EmptyView() }
|
|
||||||
)
|
|
||||||
.disabled(true)
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Evolution",
|
|
||||||
subtitle: "Migrating and reverse-migrating stores",
|
|
||||||
destination: {
|
|
||||||
Advanced.EvolutionDemo.MainView()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Menu.ItemView(
|
|
||||||
title: "Logger",
|
|
||||||
subtitle: "Implementing a custom logger",
|
|
||||||
destination: { EmptyView() }
|
|
||||||
)
|
|
||||||
.disabled(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.listStyle(GroupedListStyle())
|
|
||||||
.navigationBarTitle("CoreStore Demos")
|
|
||||||
Menu.PlaceholderView()
|
Menu.PlaceholderView()
|
||||||
}
|
}
|
||||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Menu_MainView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
Menu.MainView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Combine
|
|
||||||
import CoreStore
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
// MARK: - Menu
|
// MARK: - Menu
|
||||||
|
|
||||||
@@ -28,17 +27,3 @@ extension Menu {
|
|||||||
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
struct _Demo_Menu_PlaceholderView_Preview: PreviewProvider {
|
|
||||||
|
|
||||||
// MARK: PreviewProvider
|
|
||||||
|
|
||||||
static var previews: some View {
|
|
||||||
|
|
||||||
return Menu.PlaceholderView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -2,9 +2,202 @@
|
|||||||
// Demo
|
// Demo
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Menu
|
// MARK: - Menu
|
||||||
|
|
||||||
enum Menu {}
|
enum Menu {
|
||||||
|
|
||||||
|
// MARK: - Section
|
||||||
|
|
||||||
|
enum Section: String, CaseIterable, Hashable {
|
||||||
|
|
||||||
|
case modern = "Modern (CoreStoreObject subclasses)"
|
||||||
|
case classic = "Classic (NSManagedObject subclasses)"
|
||||||
|
case advanced = "Advanced"
|
||||||
|
|
||||||
|
var routes: [Route] {
|
||||||
|
switch self {
|
||||||
|
|
||||||
|
case .modern:
|
||||||
|
return [
|
||||||
|
.placemarks,
|
||||||
|
.timeZones,
|
||||||
|
.colorsUIKit,
|
||||||
|
.colorsSwiftUI,
|
||||||
|
.pokedex
|
||||||
|
]
|
||||||
|
|
||||||
|
case .classic:
|
||||||
|
return [
|
||||||
|
.classicColors
|
||||||
|
]
|
||||||
|
|
||||||
|
case .advanced:
|
||||||
|
return [
|
||||||
|
.accounts,
|
||||||
|
.evolution,
|
||||||
|
.logger
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Route
|
||||||
|
|
||||||
|
enum Route: String, CaseIterable, Hashable, Identifiable {
|
||||||
|
|
||||||
|
case placemarks
|
||||||
|
case timeZones
|
||||||
|
case colorsUIKit
|
||||||
|
case colorsSwiftUI
|
||||||
|
case pokedex
|
||||||
|
case classicColors
|
||||||
|
case accounts
|
||||||
|
case evolution
|
||||||
|
case logger
|
||||||
|
|
||||||
|
var id: Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .placemarks:
|
||||||
|
"Placemarks"
|
||||||
|
|
||||||
|
case .timeZones:
|
||||||
|
"Time Zones"
|
||||||
|
|
||||||
|
case .colorsUIKit:
|
||||||
|
"Colors (UIKit)"
|
||||||
|
|
||||||
|
case .colorsSwiftUI:
|
||||||
|
"Colors (SwiftUI)"
|
||||||
|
|
||||||
|
case .pokedex:
|
||||||
|
"Pokedex API"
|
||||||
|
|
||||||
|
case .classicColors:
|
||||||
|
"Colors"
|
||||||
|
|
||||||
|
case .accounts:
|
||||||
|
"Accounts"
|
||||||
|
|
||||||
|
case .evolution:
|
||||||
|
"Evolution"
|
||||||
|
|
||||||
|
case .logger:
|
||||||
|
"Logger"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var subtitle: String {
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .placemarks:
|
||||||
|
"Making changes using Transactions"
|
||||||
|
|
||||||
|
case .timeZones:
|
||||||
|
"Fetching objects and Querying raw values"
|
||||||
|
|
||||||
|
case .colorsUIKit:
|
||||||
|
"Observing list changes and single-object changes using DiffableDataSources"
|
||||||
|
|
||||||
|
case .colorsSwiftUI:
|
||||||
|
"Observing list changes and single-object changes using SwiftUI bindings"
|
||||||
|
|
||||||
|
case .pokedex:
|
||||||
|
"Importing JSON data from external source"
|
||||||
|
|
||||||
|
case .classicColors:
|
||||||
|
"Observing list changes and single-object changes using ListMonitor"
|
||||||
|
|
||||||
|
case .accounts:
|
||||||
|
"Switching between multiple persistent stores"
|
||||||
|
|
||||||
|
case .evolution:
|
||||||
|
"Migrating and reverse-migrating stores"
|
||||||
|
|
||||||
|
case .logger:
|
||||||
|
"Implementing a custom logger"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEnabled: Bool {
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
|
||||||
|
case .placemarks,
|
||||||
|
.timeZones,
|
||||||
|
.colorsUIKit,
|
||||||
|
.colorsSwiftUI,
|
||||||
|
.pokedex,
|
||||||
|
.classicColors,
|
||||||
|
.evolution:
|
||||||
|
return true
|
||||||
|
|
||||||
|
case .accounts,
|
||||||
|
.logger:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
@ViewBuilder
|
||||||
|
var destination: some View {
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .placemarks:
|
||||||
|
Modern.PlacemarksDemo.MainView()
|
||||||
|
|
||||||
|
case .timeZones:
|
||||||
|
Modern.TimeZonesDemo.MainView()
|
||||||
|
|
||||||
|
case .colorsUIKit:
|
||||||
|
Modern.ColorsDemo.MainView(
|
||||||
|
listView: { listPublisher, onPaletteTapped in
|
||||||
|
Modern.ColorsDemo.UIKit.ListView(
|
||||||
|
listPublisher: listPublisher,
|
||||||
|
onPaletteTapped: onPaletteTapped
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
},
|
||||||
|
detailView: { objectPublisher in
|
||||||
|
Modern.ColorsDemo.UIKit.DetailView(objectPublisher)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
case .colorsSwiftUI:
|
||||||
|
Modern.ColorsDemo.MainView(
|
||||||
|
listView: { listPublisher, onPaletteTapped in
|
||||||
|
Modern.ColorsDemo.SwiftUI.ListView(
|
||||||
|
listPublisher: listPublisher,
|
||||||
|
onPaletteTapped: onPaletteTapped
|
||||||
|
)
|
||||||
|
},
|
||||||
|
detailView: { objectPublisher in
|
||||||
|
Modern.ColorsDemo.SwiftUI.DetailView(objectPublisher)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
case .pokedex:
|
||||||
|
Modern.PokedexDemo.MainView(
|
||||||
|
listView: Modern.PokedexDemo.UIKit.ListView.init
|
||||||
|
)
|
||||||
|
|
||||||
|
case .classicColors:
|
||||||
|
Classic.ColorsDemo.MainView()
|
||||||
|
|
||||||
|
case .accounts, .logger:
|
||||||
|
EmptyView()
|
||||||
|
|
||||||
|
case .evolution:
|
||||||
|
Advanced.EvolutionDemo.MainView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
//
|
|
||||||
// Demo
|
|
||||||
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
// MARK: - SceneDelegate
|
|
||||||
|
|
||||||
@objc final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
||||||
|
|
||||||
// MARK: UIWindowSceneDelegate
|
|
||||||
|
|
||||||
@objc dynamic var window: UIWindow?
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: UISceneDelegate
|
|
||||||
|
|
||||||
@objc dynamic func scene(
|
|
||||||
_ scene: UIScene,
|
|
||||||
willConnectTo session: UISceneSession,
|
|
||||||
options connectionOptions: UIScene.ConnectionOptions
|
|
||||||
) {
|
|
||||||
|
|
||||||
guard case let scene as UIWindowScene = scene else {
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let window = UIWindow(windowScene: scene)
|
|
||||||
window.rootViewController = UIHostingController(
|
|
||||||
rootView: Menu.MainView()
|
|
||||||
)
|
|
||||||
self.window = window
|
|
||||||
window.makeKeyAndVisible()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,11 +41,11 @@ let dataStack = DataStack(
|
|||||||
Entity<Animal>("Animal"),
|
Entity<Animal>("Animal"),
|
||||||
Entity<Person>("Person"),
|
Entity<Person>("Person"),
|
||||||
Entity<Dog>("Dog")
|
Entity<Dog>("Dog")
|
||||||
]/*,
|
],
|
||||||
versionLock: [
|
versionLock: [
|
||||||
"Animal": [0x4a201cc685d53c0a, 0x16e6c3b561577875, 0xb032e2da61c792a0, 0xa133b801051acee4],
|
"Animal": [0x4a201cc685d53c0a, 0x16e6c3b561577875, 0xb032e2da61c792a0, 0xa133b801051acee4],
|
||||||
"Person": [0xca938eea1af4bd56, 0xbca30994506356ad, 0x7a7cc655898816ef, 0x1a4551ffedc9b214]
|
"Person": [0xca938eea1af4bd56, 0xbca30994506356ad, 0x7a7cc655898816ef, 0x1a4551ffedc9b214]
|
||||||
]*/
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
dataStack.addStorage(
|
dataStack.addStorage(
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ import CoreData
|
|||||||
/**
|
/**
|
||||||
The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`.
|
The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`.
|
||||||
*/
|
*/
|
||||||
public final class AsynchronousDataTransaction: BaseDataTransaction {
|
@_nonSendable
|
||||||
|
public nonisolated final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
|
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
|
||||||
@@ -78,6 +79,23 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
|||||||
return super.create(into)
|
return super.create(into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
|
||||||
|
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
||||||
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
*/
|
||||||
|
public override func edit<O: DynamicObject>(
|
||||||
|
_ persistentID: DynamicObjectID<O>?
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
!self.isCommitted,
|
||||||
|
"Attempted to update an entity for \(Internals.typeName(persistentID)) from an already committed \(Internals.typeName(self))."
|
||||||
|
)
|
||||||
|
return super.edit(persistentID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
|
||||||
@@ -188,7 +206,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal func autoCommit(
|
internal func autoCommit(
|
||||||
_ completion: @escaping @MainActor (
|
_ completion: @escaping @MainActor @Sendable (
|
||||||
_ hasChanges: Bool,
|
_ hasChanges: Bool,
|
||||||
_ error: CoreStoreError?
|
_ error: CoreStoreError?
|
||||||
) -> Void
|
) -> Void
|
||||||
|
|||||||
@@ -110,6 +110,19 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
|
|||||||
return self.context.fetchExisting(object)
|
return self.context.fetchExisting(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fetches the `DynamicObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||||
|
|
||||||
|
- parameter persistentID: the `DynamicObjectID` for the object
|
||||||
|
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||||
|
*/
|
||||||
|
public func fetchExisting<O: DynamicObject>(
|
||||||
|
_ persistentID: DynamicObjectID<O>
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
return self.context.fetchExisting(persistentID.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Fetches the `DynamicObject` instance in the transaction's context from an `NSManagedObjectID`.
|
Fetches the `DynamicObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||||
|
|
||||||
@@ -136,6 +149,19 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
|
|||||||
return self.context.fetchExisting(objects)
|
return self.context.fetchExisting(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fetches the `DynamicObject` instances in the transaction's context from a list of `DynamicObjectID`.
|
||||||
|
|
||||||
|
- parameter objectIDs: the `DynamicObjectID` array for the objects
|
||||||
|
- returns: the `DynamicObject` array for objects that exists in the transaction
|
||||||
|
*/
|
||||||
|
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||||
|
_ objectIDs: S
|
||||||
|
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
||||||
|
|
||||||
|
return self.context.fetchExisting(objectIDs)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Fetches the `DynamicObject` instances in the transaction's context from a list of `NSManagedObjectID`.
|
Fetches the `DynamicObject` instances in the transaction's context from a list of `NSManagedObjectID`.
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,27 @@ public /*abstract*/ class BaseDataTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
|
||||||
|
- parameter persistentID: the `DynamicObjectID` pertaining ot the `NSManagedObject` or `CoreStoreObject` type to be edited
|
||||||
|
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
*/
|
||||||
|
public func edit<O: DynamicObject>(
|
||||||
|
_ persistentID: DynamicObjectID<O>?
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
self.isRunningInAllowedQueue(),
|
||||||
|
"Attempted to update an entity for \(Internals.typeName(persistentID)) outside its designated queue."
|
||||||
|
)
|
||||||
|
guard let persistentID = persistentID else {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return self.context.fetchExisting(persistentID.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
|
||||||
|
|
||||||
|
|||||||
@@ -652,7 +652,7 @@ extension ObjectPublisher: CustomDebugStringConvertible, CoreStoreDebugStringCon
|
|||||||
|
|
||||||
return createFormattedString(
|
return createFormattedString(
|
||||||
"(", ")",
|
"(", ")",
|
||||||
("objectID", self.objectID()),
|
("managedObjectID", self.cs_id()),
|
||||||
("object", self.object as Any)
|
("object", self.object as Any)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -677,7 +677,7 @@ extension ObjectSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConv
|
|||||||
|
|
||||||
return createFormattedString(
|
return createFormattedString(
|
||||||
"(", ")",
|
"(", ")",
|
||||||
("objectID", self.objectID()),
|
("managedObjectID", self.cs_id()),
|
||||||
("dictionaryForValues", self.dictionaryForValues())
|
("dictionaryForValues", self.dictionaryForValues())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - CoreStoreDefaults
|
// MARK: - CoreStoreDefaults
|
||||||
@@ -94,6 +93,6 @@ public enum CoreStoreDefaults {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private static let defaultStackInstance: OSAllocatedUnfairLock<DataStack?> = .init(initialState: nil)
|
private static let defaultStackInstance: Internals.Mutex<DataStack?> = .init(nil)
|
||||||
private static let loggerInstance: OSAllocatedUnfairLock<(any CoreStoreLogger)?> = .init(initialState: nil)
|
private static let loggerInstance: Internals.Mutex<(any CoreStoreLogger)?> = .init(nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import Foundation
|
|||||||
/**
|
/**
|
||||||
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
||||||
*/
|
*/
|
||||||
public enum CoreStoreError: Error, CustomNSError, Hashable, @unchecked Sendable {
|
public enum CoreStoreError: Error, CustomNSError, Hashable, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A failure occured because of an unknown error.
|
A failure occured because of an unknown error.
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
|||||||
Do not call this directly. This is exposed as public only as a required initializer.
|
Do not call this directly. This is exposed as public only as a required initializer.
|
||||||
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
|
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public required init(rawObject: NSManagedObject) {
|
public required init(rawObject: NSManagedObject) {
|
||||||
|
|
||||||
self.isMeta = false
|
self.isMeta = false
|
||||||
@@ -84,6 +85,7 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
|
|||||||
Do not call this directly. This is exposed as public only as a required initializer.
|
Do not call this directly. This is exposed as public only as a required initializer.
|
||||||
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
|
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public required init(asMeta: Void) {
|
public required init(asMeta: Void) {
|
||||||
|
|
||||||
self.isMeta = true
|
self.isMeta = true
|
||||||
|
|||||||
@@ -257,20 +257,20 @@ public final class CoreStoreSchema: DynamicSchema {
|
|||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal let entitiesByConfiguration: [String: Set<DynamicEntity>]
|
internal nonisolated(unsafe) let entitiesByConfiguration: [String: Set<DynamicEntity>]
|
||||||
|
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue", qos: .userInteractive)
|
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue", qos: .userInteractive)
|
||||||
|
|
||||||
private let allEntities: Set<DynamicEntity>
|
private nonisolated(unsafe) let allEntities: Set<DynamicEntity>
|
||||||
|
|
||||||
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
private nonisolated(unsafe) var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
|
||||||
private var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
private nonisolated(unsafe) var customGettersSettersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomGetterSetter]] = [:]
|
||||||
private var customInitializersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]] = [:]
|
private nonisolated(unsafe) var customInitializersByEntity: [DynamicEntity: [KeyPathString: CoreStoreManagedObject.CustomInitializer]] = [:]
|
||||||
private var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
private nonisolated(unsafe) var fieldCodersByEntity: [DynamicEntity: [KeyPathString: Internals.AnyFieldCoder]] = [:]
|
||||||
private weak var cachedRawModel: NSManagedObjectModel?
|
private nonisolated(unsafe) weak var cachedRawModel: NSManagedObjectModel?
|
||||||
|
|
||||||
private func entityDescription(
|
private func entityDescription(
|
||||||
for entity: DynamicEntity,
|
for entity: DynamicEntity,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import Foundation
|
|||||||
/**
|
/**
|
||||||
A `SchemaMappingProvider` that accepts custom mappings for some entities. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible.
|
A `SchemaMappingProvider` that accepts custom mappings for some entities. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible.
|
||||||
*/
|
*/
|
||||||
public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
public final class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The source model version for the mapping.
|
The source model version for the mapping.
|
||||||
@@ -78,7 +78,7 @@ public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
|||||||
/**
|
/**
|
||||||
Provides the type of mapping for an entity. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible. Any conflicts or ambiguity will raise an assertion.
|
Provides the type of mapping for an entity. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible. Any conflicts or ambiguity will raise an assertion.
|
||||||
*/
|
*/
|
||||||
public enum CustomMapping: Hashable {
|
public enum CustomMapping: Hashable, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The `sourceEntity` is meant to be removed from the source `DynamicSchema` and should not be migrated to the destination `DynamicSchema`.
|
The `sourceEntity` is meant to be removed from the source `DynamicSchema` and should not be migrated to the destination `DynamicSchema`.
|
||||||
@@ -105,7 +105,7 @@ public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
|||||||
- parameter sourceObject: a proxy object representing the source entity. The properties can be accessed via keyPath.
|
- parameter sourceObject: a proxy object representing the source entity. The properties can be accessed via keyPath.
|
||||||
- parameter createDestinationObject: the closure to create the object for the destination entity. The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight). The object is created lazily and executing the closure multiple times will return the same instance. The destination object's properties can be accessed and updated via keyPath.
|
- parameter createDestinationObject: the closure to create the object for the destination entity. The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight). The object is created lazily and executing the closure multiple times will return the same instance. The destination object's properties can be accessed and updated via keyPath.
|
||||||
*/
|
*/
|
||||||
public typealias Transformer = (
|
public typealias Transformer = @Sendable (
|
||||||
_ sourceObject: UnsafeSourceObject,
|
_ sourceObject: UnsafeSourceObject,
|
||||||
_ createDestinationObject: () -> UnsafeDestinationObject
|
_ createDestinationObject: () -> UnsafeDestinationObject
|
||||||
) throws(any Swift.Error) -> Void
|
) throws(any Swift.Error) -> Void
|
||||||
@@ -737,7 +737,10 @@ public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
|
|||||||
.transformEntity(
|
.transformEntity(
|
||||||
sourceEntity: sourceEntityName,
|
sourceEntity: sourceEntityName,
|
||||||
destinationEntity: destinationEntityName,
|
destinationEntity: destinationEntityName,
|
||||||
transformer: CustomMapping.inferredTransformation
|
transformer: {
|
||||||
|
|
||||||
|
return try CustomMapping.inferredTransformation($0, $1)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ extension DataStack {
|
|||||||
_ object: O
|
_ object: O
|
||||||
) -> ObjectPublisher<O> {
|
) -> ObjectPublisher<O> {
|
||||||
|
|
||||||
return self.publishObject(object.cs_id())
|
let context = self.unsafeContext()
|
||||||
|
return context.objectPublisher(managedObjectID: object.cs_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +58,7 @@ extension DataStack {
|
|||||||
) -> ObjectPublisher<O> {
|
) -> ObjectPublisher<O> {
|
||||||
|
|
||||||
let context = self.unsafeContext()
|
let context = self.unsafeContext()
|
||||||
return context.objectPublisher(objectID: objectID)
|
return context.objectPublisher(managedObjectID: objectID.managedObjectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,8 +24,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@preconcurrency import CoreData
|
import CoreData
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - DataStack
|
// MARK: - DataStack
|
||||||
@@ -50,7 +49,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func addStorage<T>(
|
public func addStorage<T>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
completion: @escaping @MainActor @Sendable (SetupResult<T>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.coordinator.performAsynchronously {
|
self.coordinator.performAsynchronously {
|
||||||
@@ -111,7 +110,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func addStorage<T: LocalStorage>(
|
public func addStorage<T: LocalStorage>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
completion: @escaping @MainActor (SetupResult<T>) -> Void
|
completion: @escaping @MainActor @Sendable (SetupResult<T>) -> Void
|
||||||
) -> Progress? {
|
) -> Progress? {
|
||||||
|
|
||||||
let fileURL = storage.fileURL
|
let fileURL = storage.fileURL
|
||||||
@@ -274,7 +273,7 @@ extension DataStack {
|
|||||||
*/
|
*/
|
||||||
public func upgradeStorageIfNeeded<T: LocalStorage>(
|
public func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
completion: @escaping @MainActor (MigrationResult) -> Void
|
completion: @escaping @MainActor @Sendable (MigrationResult) -> Void
|
||||||
) throws(CoreStoreError) -> Progress? {
|
) throws(CoreStoreError) -> Progress? {
|
||||||
|
|
||||||
return try self.coordinator.performSynchronously {
|
return try self.coordinator.performSynchronously {
|
||||||
@@ -386,7 +385,7 @@ extension DataStack {
|
|||||||
private func upgradeStorageIfNeeded<T: LocalStorage>(
|
private func upgradeStorageIfNeeded<T: LocalStorage>(
|
||||||
_ storage: T,
|
_ storage: T,
|
||||||
metadata: [String: Any],
|
metadata: [String: Any],
|
||||||
completion: @escaping @MainActor (MigrationResult) -> Void
|
completion: @escaping @MainActor @Sendable (MigrationResult) -> Void
|
||||||
) -> Progress? {
|
) -> Progress? {
|
||||||
|
|
||||||
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
|
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
|
||||||
@@ -433,8 +432,8 @@ extension DataStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let migrationTypes = migrationSteps.map { $0.migrationType }
|
let migrationTypes = migrationSteps.map { $0.migrationType }
|
||||||
let migrationState: OSAllocatedUnfairLock<(migrationResult: MigrationResult?, cancelled: Bool)> = .init(
|
let migrationState: Internals.Mutex<(migrationResult: MigrationResult?, cancelled: Bool)> = .init(
|
||||||
initialState: (
|
(
|
||||||
migrationResult: nil,
|
migrationResult: nil,
|
||||||
cancelled: false
|
cancelled: false
|
||||||
)
|
)
|
||||||
@@ -451,6 +450,9 @@ extension DataStack {
|
|||||||
let childProgress = Progress(parent: progress, userInfo: nil)
|
let childProgress = Progress(parent: progress, userInfo: nil)
|
||||||
childProgress.totalUnitCount = 100
|
childProgress.totalUnitCount = 100
|
||||||
|
|
||||||
|
nonisolated(unsafe) let sourceModel = sourceModel
|
||||||
|
nonisolated(unsafe) let destinationModel = destinationModel
|
||||||
|
nonisolated(unsafe) let mappingModel = mappingModel
|
||||||
operations.append(
|
operations.append(
|
||||||
BlockOperation { [weak self] in
|
BlockOperation { [weak self] in
|
||||||
|
|
||||||
@@ -508,13 +510,11 @@ extension DataStack {
|
|||||||
operations.forEach { migrationOperation.addDependency($0) }
|
operations.forEach { migrationOperation.addDependency($0) }
|
||||||
migrationOperation.addExecutionBlock { () -> Void in
|
migrationOperation.addExecutionBlock { () -> Void in
|
||||||
|
|
||||||
|
let migrationResult = migrationState.withLock { $0.migrationResult }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
||||||
progress.setProgressHandler(nil)
|
progress.setProgressHandler(nil)
|
||||||
completion(
|
completion(migrationResult ?? .success(migrationTypes))
|
||||||
migrationState.withLock { $0.migrationResult }
|
|
||||||
?? .success(migrationTypes)
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -620,7 +620,7 @@ extension DataStack {
|
|||||||
let estimatedTime: TimeInterval = 60 * 3 // 3 mins
|
let estimatedTime: TimeInterval = 60 * 3 // 3 mins
|
||||||
let interval: TimeInterval = 1
|
let interval: TimeInterval = 1
|
||||||
let fakeTotalUnitCount: Float = 0.9 * Float(progress.totalUnitCount)
|
let fakeTotalUnitCount: Float = 0.9 * Float(progress.totalUnitCount)
|
||||||
let fakeProgress: OSAllocatedUnfairLock<Float> = .init(initialState: 0)
|
let fakeProgress: Internals.Mutex<Float> = .init(0)
|
||||||
|
|
||||||
@Sendable
|
@Sendable
|
||||||
func recursiveCheck() {
|
func recursiveCheck() {
|
||||||
@@ -656,10 +656,12 @@ extension DataStack {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
timerQueue.sync {
|
withoutActuallyEscaping(timerQueue.sync) { escapingClosure in
|
||||||
|
escapingClosure {
|
||||||
|
|
||||||
fakeProgress.withLock({ $0 = 1.0 })
|
fakeProgress.withLock({ $0 = 1.0 })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ = try? storage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
|
_ = try? storage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
|
||||||
progress.completedUnitCount = progress.totalUnitCount
|
progress.completedUnitCount = progress.totalUnitCount
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ extension DataStack {
|
|||||||
- parameter object: the `DynamicObject` to observe changes from
|
- parameter object: the `DynamicObject` to observe changes from
|
||||||
- returns: an `ObjectMonitor` that monitors changes to `object`
|
- returns: an `ObjectMonitor` that monitors changes to `object`
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorObject<O: DynamicObject>(
|
public func monitorObject<O: DynamicObject>(
|
||||||
_ object: O
|
_ object: O
|
||||||
) -> ObjectMonitor<O> {
|
) -> ObjectMonitor<O> {
|
||||||
@@ -55,6 +56,7 @@ extension DataStack {
|
|||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ fetchClauses: FetchClause...
|
_ fetchClauses: FetchClause...
|
||||||
@@ -70,6 +72,7 @@ extension DataStack {
|
|||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ fetchClauses: [FetchClause]
|
_ fetchClauses: [FetchClause]
|
||||||
@@ -107,6 +110,7 @@ extension DataStack {
|
|||||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||||
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType`
|
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType`
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorList<B: FetchChainableBuilderType>(
|
public func monitorList<B: FetchChainableBuilderType>(
|
||||||
_ clauseChain: B
|
_ clauseChain: B
|
||||||
) -> ListMonitor<B.ObjectType> {
|
) -> ListMonitor<B.ObjectType> {
|
||||||
@@ -124,6 +128,7 @@ extension DataStack {
|
|||||||
- parameter from: a `From` clause indicating the entity type
|
- parameter from: a `From` clause indicating the entity type
|
||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
@@ -144,6 +149,7 @@ extension DataStack {
|
|||||||
- parameter from: a `From` clause indicating the entity type
|
- parameter from: a `From` clause indicating the entity type
|
||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorList<O>(
|
public func monitorList<O>(
|
||||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
@@ -187,6 +193,7 @@ extension DataStack {
|
|||||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||||
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorList<B: FetchChainableBuilderType>(
|
public func monitorList<B: FetchChainableBuilderType>(
|
||||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||||
_ clauseChain: B
|
_ clauseChain: B
|
||||||
@@ -207,6 +214,7 @@ extension DataStack {
|
|||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ sectionBy: SectionBy<O>,
|
_ sectionBy: SectionBy<O>,
|
||||||
@@ -228,6 +236,7 @@ extension DataStack {
|
|||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
_ sectionBy: SectionBy<O>,
|
_ sectionBy: SectionBy<O>,
|
||||||
@@ -268,6 +277,7 @@ extension DataStack {
|
|||||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||||
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType`
|
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType`
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||||
_ clauseChain: B
|
_ clauseChain: B
|
||||||
) -> ListMonitor<B.ObjectType> {
|
) -> ListMonitor<B.ObjectType> {
|
||||||
@@ -287,6 +297,7 @@ extension DataStack {
|
|||||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
@@ -310,6 +321,7 @@ extension DataStack {
|
|||||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorSectionedList<O>(
|
public func monitorSectionedList<O>(
|
||||||
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<O>) -> Void,
|
||||||
_ from: From<O>,
|
_ from: From<O>,
|
||||||
@@ -355,6 +367,7 @@ extension DataStack {
|
|||||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||||
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
public func monitorSectionedList<B: SectionMonitorBuilderType>(
|
||||||
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
createAsynchronously: @escaping @Sendable (ListMonitor<B.ObjectType>) -> Void,
|
||||||
_ clauseChain: B
|
_ clauseChain: B
|
||||||
|
|||||||
@@ -46,6 +46,19 @@ extension DataStack: FetchableSource, QueryableSource {
|
|||||||
return self.mainContext.fetchExisting(object)
|
return self.mainContext.fetchExisting(object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||||
|
|
||||||
|
- parameter persistentID: the `DynamicObjectID` for the object
|
||||||
|
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||||
|
*/
|
||||||
|
public func fetchExisting<O: DynamicObject>(
|
||||||
|
_ persistentID: DynamicObjectID<O>
|
||||||
|
) -> O? {
|
||||||
|
|
||||||
|
return self.mainContext.fetchExisting(persistentID.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
Fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||||
|
|
||||||
@@ -72,6 +85,19 @@ extension DataStack: FetchableSource, QueryableSource {
|
|||||||
return self.mainContext.fetchExisting(objects)
|
return self.mainContext.fetchExisting(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `DynamicObjectID`.
|
||||||
|
|
||||||
|
- parameter objectIDs: the `DynamicObjectID` array for the objects
|
||||||
|
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
|
||||||
|
*/
|
||||||
|
public func fetchExisting<O: DynamicObject, S: Sequence>(
|
||||||
|
_ objectIDs: S
|
||||||
|
) -> [O] where S.Iterator.Element == DynamicObjectID<O> {
|
||||||
|
|
||||||
|
return self.mainContext.fetchExisting(objectIDs)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
Fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ extension DataStack {
|
|||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift.Error) -> T,
|
||||||
sourceIdentifier: (any Sendable)? = nil,
|
sourceIdentifier: (any Sendable)? = nil,
|
||||||
completion: @escaping @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
completion: @escaping @MainActor @Sendable (AsynchronousDataTransaction.Result<T>) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.perform(
|
self.perform(
|
||||||
@@ -67,8 +67,8 @@ extension DataStack {
|
|||||||
_ transaction: AsynchronousDataTransaction
|
_ transaction: AsynchronousDataTransaction
|
||||||
) throws(any Swift.Error) -> T,
|
) throws(any Swift.Error) -> T,
|
||||||
sourceIdentifier: (any Sendable)? = nil,
|
sourceIdentifier: (any Sendable)? = nil,
|
||||||
success: @escaping @Sendable (sending T) -> Void,
|
success: @escaping @MainActor @Sendable (sending T) -> Void,
|
||||||
failure: @escaping @Sendable (CoreStoreError) -> Void
|
failure: @escaping @MainActor @Sendable (CoreStoreError) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
nonisolated(unsafe) let transaction = AsynchronousDataTransaction(
|
nonisolated(unsafe) let transaction = AsynchronousDataTransaction(
|
||||||
@@ -186,6 +186,7 @@ extension DataStack {
|
|||||||
/**
|
/**
|
||||||
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `DataStack`.
|
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `DataStack`.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func refreshAndMergeAllObjects() {
|
public func refreshAndMergeAllObjects() {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ extension DataStack {
|
|||||||
|
|
||||||
// MARK: - AddStorageSubscription
|
// MARK: - AddStorageSubscription
|
||||||
|
|
||||||
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription, @unchecked Sendable
|
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription
|
||||||
where S.Input == Output, S.Failure == CoreStoreError {
|
where S.Input == Output, S.Failure == CoreStoreError {
|
||||||
|
|
||||||
// MARK: FilePrivate
|
// MARK: FilePrivate
|
||||||
@@ -116,14 +116,15 @@ extension DataStack {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
nonisolated(unsafe) var progress: Progress? = nil
|
nonisolated(unsafe) var progress: Progress? = nil
|
||||||
|
nonisolated(unsafe) weak let weakSelf = self as Optional
|
||||||
progress = self.dataStack.addStorage(
|
progress = self.dataStack.addStorage(
|
||||||
self.storage,
|
self.storage,
|
||||||
completion: { [weak self] result in
|
completion: { result in
|
||||||
|
|
||||||
progress?.setProgressHandler(nil)
|
progress?.setProgressHandler(nil)
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let self = self,
|
let self = weakSelf,
|
||||||
let subscriber = self.subscriber
|
let subscriber = self.subscriber
|
||||||
else {
|
else {
|
||||||
|
|
||||||
@@ -151,12 +152,12 @@ extension DataStack {
|
|||||||
)
|
)
|
||||||
if let progress = progress {
|
if let progress = progress {
|
||||||
|
|
||||||
Internals.mainActorImmediate { @MainActor [weak self] in
|
Internals.mainActorImmediate { @MainActor in
|
||||||
|
|
||||||
progress.setProgressHandler { progress in
|
progress.setProgressHandler { progress in
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let self = self,
|
let self = weakSelf,
|
||||||
let subscriber = self.subscriber
|
let subscriber = self.subscriber
|
||||||
else {
|
else {
|
||||||
|
|
||||||
|
|||||||
+25
-26
@@ -32,7 +32,7 @@ import CoreData
|
|||||||
/**
|
/**
|
||||||
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
|
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
|
||||||
*/
|
*/
|
||||||
public final class DataStack: Equatable, @unchecked Sendable {
|
public final class DataStack: Equatable, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The resolved application name, used by the `DataStack` as the default Xcode model name (.xcdatamodel filename) if not explicitly provided.
|
The resolved application name, used by the `DataStack` as the default Xcode model name (.xcdatamodel filename) if not explicitly provided.
|
||||||
@@ -398,7 +398,7 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
- parameter completion: the closure to execute after all persistent stores are removed
|
- parameter completion: the closure to execute after all persistent stores are removed
|
||||||
*/
|
*/
|
||||||
public func unsafeRemoveAllPersistentStores(
|
public func unsafeRemoveAllPersistentStores(
|
||||||
completion: @escaping @MainActor () -> Void = {}
|
completion: @escaping @MainActor @Sendable () -> Void = {}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
let coordinator = self.coordinator
|
let coordinator = self.coordinator
|
||||||
@@ -466,7 +466,6 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
internal let mainContext: NSManagedObjectContext
|
internal let mainContext: NSManagedObjectContext
|
||||||
internal let schemaHistory: SchemaHistory
|
internal let schemaHistory: SchemaHistory
|
||||||
internal let childTransactionQueue = DispatchQueue.serial("com.coreStore.dataStack.childTransactionQueue", qos: .utility)
|
internal let childTransactionQueue = DispatchQueue.serial("com.coreStore.dataStack.childTransactionQueue", qos: .utility)
|
||||||
internal let storeMetadataLock: NSRecursiveLock = .init()
|
|
||||||
internal let migrationQueue: OperationQueue = Internals.with {
|
internal let migrationQueue: OperationQueue = Internals.with {
|
||||||
|
|
||||||
let migrationQueue = OperationQueue()
|
let migrationQueue = OperationQueue()
|
||||||
@@ -487,15 +486,14 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal func persistentStores(
|
internal func persistentStores(
|
||||||
for entityIdentifier: Internals.EntityIdentifier
|
for entityIdentifier: sending Internals.EntityIdentifier
|
||||||
) -> [NSPersistentStore]? {
|
) -> [NSPersistentStore]? {
|
||||||
|
|
||||||
self.storeMetadataLock.lock()
|
return self.storeMetadataLock.withLockUnchecked { metadata in
|
||||||
defer {
|
|
||||||
self.storeMetadataLock.unlock()
|
return metadata.finalConfigurationsByEntityIdentifier[entityIdentifier]?
|
||||||
|
.map({ metadata.persistentStoresByFinalConfiguration[$0]! }) ?? []
|
||||||
}
|
}
|
||||||
return self.finalConfigurationsByEntityIdentifier[entityIdentifier]?
|
|
||||||
.map({ self.persistentStoresByFinalConfiguration[$0]! }) ?? []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func persistentStore(
|
internal func persistentStore(
|
||||||
@@ -504,16 +502,14 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
inferStoreIfPossible: Bool
|
inferStoreIfPossible: Bool
|
||||||
) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
||||||
|
|
||||||
self.storeMetadataLock.lock()
|
return self.storeMetadataLock.withLockUnchecked { metadata in
|
||||||
defer {
|
|
||||||
self.storeMetadataLock.unlock()
|
let configurationsForEntity = metadata.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
|
||||||
}
|
|
||||||
let configurationsForEntity = self.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
|
|
||||||
if let configuration = configuration {
|
if let configuration = configuration {
|
||||||
|
|
||||||
if configurationsForEntity.contains(configuration) {
|
if configurationsForEntity.contains(configuration) {
|
||||||
|
|
||||||
return (store: self.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
|
return (store: metadata.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
|
||||||
}
|
}
|
||||||
else if !inferStoreIfPossible {
|
else if !inferStoreIfPossible {
|
||||||
|
|
||||||
@@ -527,12 +523,13 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
return (store: nil, isAmbiguous: false)
|
return (store: nil, isAmbiguous: false)
|
||||||
|
|
||||||
case 1 where inferStoreIfPossible:
|
case 1 where inferStoreIfPossible:
|
||||||
return (store: self.persistentStoresByFinalConfiguration[configurationsForEntity.first!], isAmbiguous: false)
|
return (store: metadata.persistentStoresByFinalConfiguration[configurationsForEntity.first!], isAmbiguous: false)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (store: nil, isAmbiguous: true)
|
return (store: nil, isAmbiguous: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal func createPersistentStoreFromStorage(
|
internal func createPersistentStoreFromStorage(
|
||||||
_ storage: StorageInterface,
|
_ storage: StorageInterface,
|
||||||
@@ -550,13 +547,10 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
||||||
self.storeMetadataLock.lock()
|
self.storeMetadataLock.withLock { metadata in
|
||||||
defer {
|
|
||||||
self.storeMetadataLock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
let configurationName = persistentStore.configurationName
|
let configurationName = persistentStore.configurationName
|
||||||
self.persistentStoresByFinalConfiguration[configurationName] = persistentStore
|
metadata.persistentStoresByFinalConfiguration[configurationName] = persistentStore
|
||||||
for entityDescription in (self.coordinator.managedObjectModel.entities(forConfigurationName: configurationName) ?? []) {
|
for entityDescription in (self.coordinator.managedObjectModel.entities(forConfigurationName: configurationName) ?? []) {
|
||||||
|
|
||||||
let managedObjectClassName = entityDescription.managedObjectClassName!
|
let managedObjectClassName = entityDescription.managedObjectClassName!
|
||||||
@@ -565,11 +559,12 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
"The class \(Internals.typeName(managedObjectClassName)) for the entity \(Internals.typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
|
"The class \(Internals.typeName(managedObjectClassName)) for the entity \(Internals.typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
|
||||||
)
|
)
|
||||||
let entityIdentifier = Internals.EntityIdentifier(entityDescription)
|
let entityIdentifier = Internals.EntityIdentifier(entityDescription)
|
||||||
if self.finalConfigurationsByEntityIdentifier[entityIdentifier] == nil {
|
if metadata.finalConfigurationsByEntityIdentifier[entityIdentifier] == nil {
|
||||||
|
|
||||||
self.finalConfigurationsByEntityIdentifier[entityIdentifier] = []
|
metadata.finalConfigurationsByEntityIdentifier[entityIdentifier] = []
|
||||||
|
}
|
||||||
|
metadata.finalConfigurationsByEntityIdentifier[entityIdentifier]?.insert(configurationName)
|
||||||
}
|
}
|
||||||
self.finalConfigurationsByEntityIdentifier[entityIdentifier]?.insert(configurationName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storage.cs_didAddToDataStack(self)
|
storage.cs_didAddToDataStack(self)
|
||||||
@@ -586,8 +581,12 @@ public final class DataStack: Equatable, @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private var persistentStoresByFinalConfiguration = [String: NSPersistentStore]()
|
private let storeMetadataLock: Internals.Mutex<
|
||||||
private var finalConfigurationsByEntityIdentifier = [Internals.EntityIdentifier: Set<String>]()
|
(
|
||||||
|
persistentStoresByFinalConfiguration: [String: NSPersistentStore],
|
||||||
|
finalConfigurationsByEntityIdentifier: [Internals.EntityIdentifier: Set<String>]
|
||||||
|
)
|
||||||
|
> = .init(([:], [:]))
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ extension DiffableDataSource {
|
|||||||
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
|
||||||
|
|
||||||
return self.dispatcher.itemIdentifier(for: indexPath)
|
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||||
|
.map(O.ObjectID.init(managedObjectID:))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,7 +229,7 @@ extension DiffableDataSource {
|
|||||||
*/
|
*/
|
||||||
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
|
||||||
|
|
||||||
return self.dispatcher.indexPath(for: itemID)
|
return self.dispatcher.indexPath(for: itemID.managedObjectID)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,6 +255,11 @@ extension DiffableDataSource {
|
|||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
|
||||||
|
|
||||||
|
internal func itemID(for indexPath: IndexPath) -> NSManagedObjectID? {
|
||||||
|
|
||||||
|
return self.dispatcher.itemIdentifier(for: indexPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ extension DiffableDataSource {
|
|||||||
cellForItemAt indexPath: IndexPath
|
cellForItemAt indexPath: IndexPath
|
||||||
) -> UICollectionViewCell {
|
) -> UICollectionViewCell {
|
||||||
|
|
||||||
guard let objectID = self.itemID(for: indexPath) else {
|
guard let objectID: NSManagedObjectID = self.itemID(for: indexPath) else {
|
||||||
|
|
||||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ extension DiffableDataSource {
|
|||||||
public init(
|
public init(
|
||||||
tableView: UITableView,
|
tableView: UITableView,
|
||||||
dataStack: DataStack,
|
dataStack: DataStack,
|
||||||
cellProvider: @escaping @MainActor (UITableView, IndexPath, O) -> UITableViewCell?
|
cellProvider: @escaping @MainActor @Sendable (UITableView, IndexPath, O) -> UITableViewCell?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.cellProvider = cellProvider
|
self.cellProvider = cellProvider
|
||||||
@@ -150,7 +150,7 @@ extension DiffableDataSource {
|
|||||||
cellForRowAt indexPath: IndexPath
|
cellForRowAt indexPath: IndexPath
|
||||||
) -> UITableViewCell {
|
) -> UITableViewCell {
|
||||||
|
|
||||||
guard let objectID = self.itemID(for: indexPath) else {
|
guard let objectID: NSManagedObjectID = self.itemID(for: indexPath) else {
|
||||||
|
|
||||||
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,7 +220,10 @@ extension DiffableDataSource.Target {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
animated: animated,
|
animated: animated,
|
||||||
completion: group.leave
|
completion: {
|
||||||
|
|
||||||
|
group.leave()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-11
@@ -32,16 +32,19 @@ import CoreData
|
|||||||
/**
|
/**
|
||||||
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
|
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
|
||||||
*/
|
*/
|
||||||
public protocol DynamicObject: AnyObject {
|
@_nonSendable
|
||||||
|
public nonisolated protocol DynamicObject: AnyObject, SendableMetatype {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The object ID for this instance
|
The object ID for this instance
|
||||||
*/
|
*/
|
||||||
typealias ObjectID = NSManagedObjectID
|
typealias ObjectID = DynamicObjectID<Self>
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_forceCreate(
|
static func cs_forceCreate(
|
||||||
entityDescription: NSEntityDescription,
|
entityDescription: NSEntityDescription,
|
||||||
into context: NSManagedObjectContext,
|
into context: NSManagedObjectContext,
|
||||||
@@ -51,14 +54,16 @@ public protocol DynamicObject: AnyObject {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_snapshotDictionary(
|
static func cs_snapshotDictionary(
|
||||||
id: ObjectID,
|
managedObjectID: NSManagedObjectID,
|
||||||
context: NSManagedObjectContext
|
context: NSManagedObjectContext
|
||||||
) -> [String: Any]?
|
) -> [String: Any]?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_fromRaw(
|
static func cs_fromRaw(
|
||||||
object: NSManagedObject
|
object: NSManagedObject
|
||||||
) -> Self
|
) -> Self
|
||||||
@@ -66,6 +71,7 @@ public protocol DynamicObject: AnyObject {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_matches(
|
static func cs_matches(
|
||||||
object: NSManagedObject
|
object: NSManagedObject
|
||||||
) -> Bool
|
) -> Bool
|
||||||
@@ -73,16 +79,26 @@ public protocol DynamicObject: AnyObject {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
func cs_toRaw() -> NSManagedObject
|
func cs_toRaw() -> NSManagedObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
func cs_id() -> ObjectID
|
@_spi(Internals)
|
||||||
|
func cs_id() -> NSManagedObjectID
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DynamicObject {
|
extension DynamicObject {
|
||||||
|
|
||||||
|
// MARK: Public
|
||||||
|
|
||||||
|
public func persistentID() -> DynamicObjectID<Self> {
|
||||||
|
|
||||||
|
return .init(managedObjectID: self.cs_id())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal func runtimeType() -> Self.Type {
|
internal func runtimeType() -> Self.Type {
|
||||||
@@ -99,6 +115,7 @@ extension NSManagedObject: DynamicObject {
|
|||||||
|
|
||||||
// MARK: DynamicObject
|
// MARK: DynamicObject
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public class func cs_forceCreate(
|
public class func cs_forceCreate(
|
||||||
entityDescription: NSEntityDescription,
|
entityDescription: NSEntityDescription,
|
||||||
into context: NSManagedObjectContext,
|
into context: NSManagedObjectContext,
|
||||||
@@ -113,12 +130,13 @@ extension NSManagedObject: DynamicObject {
|
|||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public class func cs_snapshotDictionary(
|
public class func cs_snapshotDictionary(
|
||||||
id: ObjectID,
|
managedObjectID: NSManagedObjectID,
|
||||||
context: NSManagedObjectContext
|
context: NSManagedObjectContext
|
||||||
) -> [String: Any]? {
|
) -> [String: Any]? {
|
||||||
|
|
||||||
guard let object = context.fetchExisting(id) as NSManagedObject? else {
|
guard let object = context.fetchExisting(managedObjectID) as NSManagedObject? else {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -131,6 +149,7 @@ extension NSManagedObject: DynamicObject {
|
|||||||
return dictionary
|
return dictionary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
||||||
|
|
||||||
#if swift(>=5.9)
|
#if swift(>=5.9)
|
||||||
@@ -143,6 +162,7 @@ extension NSManagedObject: DynamicObject {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public static func cs_matches(
|
public static func cs_matches(
|
||||||
object: NSManagedObject
|
object: NSManagedObject
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
@@ -150,12 +170,14 @@ extension NSManagedObject: DynamicObject {
|
|||||||
return object.isKind(of: self)
|
return object.isKind(of: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public func cs_toRaw() -> NSManagedObject {
|
public func cs_toRaw() -> NSManagedObject {
|
||||||
|
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
public func cs_id() -> ObjectID {
|
@_spi(Internals)
|
||||||
|
public func cs_id() -> NSManagedObjectID {
|
||||||
|
|
||||||
return self.objectID
|
return self.objectID
|
||||||
}
|
}
|
||||||
@@ -168,6 +190,7 @@ extension CoreStoreObject {
|
|||||||
|
|
||||||
// MARK: DynamicObject
|
// MARK: DynamicObject
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public class func cs_forceCreate(
|
public class func cs_forceCreate(
|
||||||
entityDescription: NSEntityDescription,
|
entityDescription: NSEntityDescription,
|
||||||
into context: NSManagedObjectContext,
|
into context: NSManagedObjectContext,
|
||||||
@@ -183,8 +206,9 @@ extension CoreStoreObject {
|
|||||||
return self.cs_fromRaw(object: object)
|
return self.cs_fromRaw(object: object)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public class func cs_snapshotDictionary(
|
public class func cs_snapshotDictionary(
|
||||||
id: ObjectID,
|
managedObjectID: NSManagedObjectID,
|
||||||
context: NSManagedObjectContext
|
context: NSManagedObjectContext
|
||||||
) -> [String: Any]? {
|
) -> [String: Any]? {
|
||||||
|
|
||||||
@@ -240,7 +264,7 @@ extension CoreStoreObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard let object = context.fetchExisting(id) as CoreStoreObject? else {
|
guard let object = context.fetchExisting(managedObjectID) as CoreStoreObject? else {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -253,7 +277,7 @@ extension CoreStoreObject {
|
|||||||
else {
|
else {
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let object = context.fetchExisting(id) as CoreStoreObject?,
|
let object = context.fetchExisting(managedObjectID) as CoreStoreObject?,
|
||||||
let rawObject = object.rawObject,
|
let rawObject = object.rawObject,
|
||||||
!rawObject.isDeleted
|
!rawObject.isDeleted
|
||||||
else {
|
else {
|
||||||
@@ -292,6 +316,7 @@ extension CoreStoreObject {
|
|||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
public class func cs_fromRaw(object: NSManagedObject) -> Self {
|
||||||
|
|
||||||
if let coreStoreObject = object.coreStoreObject {
|
if let coreStoreObject = object.coreStoreObject {
|
||||||
@@ -310,6 +335,7 @@ extension CoreStoreObject {
|
|||||||
return coreStoreObject
|
return coreStoreObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public static func cs_matches(
|
public static func cs_matches(
|
||||||
object: NSManagedObject
|
object: NSManagedObject
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
@@ -321,12 +347,14 @@ extension CoreStoreObject {
|
|||||||
return (self as AnyClass).isSubclass(of: type as AnyClass)
|
return (self as AnyClass).isSubclass(of: type as AnyClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
public func cs_toRaw() -> NSManagedObject {
|
public func cs_toRaw() -> NSManagedObject {
|
||||||
|
|
||||||
return self.rawObject!
|
return self.rawObject!
|
||||||
}
|
}
|
||||||
|
|
||||||
public func cs_id() -> ObjectID {
|
@_spi(Internals)
|
||||||
|
public func cs_id() -> NSManagedObjectID {
|
||||||
|
|
||||||
return self.rawObject!.objectID
|
return self.rawObject!.objectID
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
//
|
||||||
|
// DynamicObject.swift
|
||||||
|
// CoreStore
|
||||||
|
//
|
||||||
|
// Copyright © 2026 John Rommel Estropia
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - DynamicObjectID
|
||||||
|
|
||||||
|
public struct DynamicObjectID<O: DynamicObject>: Hashable, ObjectRepresentation, Sendable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||||
|
*/
|
||||||
|
public let entityClass: O.Type
|
||||||
|
|
||||||
|
public init(
|
||||||
|
managedObjectID: NSManagedObjectID
|
||||||
|
) {
|
||||||
|
|
||||||
|
self.init(
|
||||||
|
entityClass: O.self,
|
||||||
|
managedObjectID: managedObjectID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
entityClass: O.Type,
|
||||||
|
managedObjectID: NSManagedObjectID
|
||||||
|
) {
|
||||||
|
|
||||||
|
Internals.assert(
|
||||||
|
Internals.EntityIdentifier(entityClass) == Internals.EntityIdentifier(managedObjectID.entity),
|
||||||
|
"The \(Internals.typeName(managedObjectID)) does not belong to the entity \(Internals.typeName(entityClass))."
|
||||||
|
)
|
||||||
|
|
||||||
|
self.entityClass = entityClass
|
||||||
|
self.managedObjectID = managedObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Equatable
|
||||||
|
|
||||||
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
||||||
|
|
||||||
|
return lhs.managedObjectID == rhs.managedObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Hashable
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
|
||||||
|
hasher.combine(self.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: AnyObjectRepresentation
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
|
public func cs_dataStack() -> DataStack? {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@_spi(Internals)
|
||||||
|
public func cs_id() -> NSManagedObjectID {
|
||||||
|
|
||||||
|
return self.managedObjectID
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: ObjectRepresentation
|
||||||
|
|
||||||
|
public typealias ObjectType = O
|
||||||
|
|
||||||
|
public func asPublisher(in dataStack: DataStack) -> ObjectPublisher<ObjectType> {
|
||||||
|
|
||||||
|
let context = dataStack.unsafeContext()
|
||||||
|
return context.objectPublisher(managedObjectID: self.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func asReadOnly(in dataStack: DataStack) -> ObjectType? {
|
||||||
|
|
||||||
|
let context = dataStack.unsafeContext()
|
||||||
|
return context.fetchExisting(self.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func asEditable(in transaction: BaseDataTransaction) -> ObjectType? {
|
||||||
|
|
||||||
|
let context = transaction.unsafeContext()
|
||||||
|
return context.fetchExisting(self.managedObjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func asSnapshot(in dataStack: DataStack) -> ObjectSnapshot<ObjectType>? {
|
||||||
|
|
||||||
|
let context = dataStack.unsafeContext()
|
||||||
|
return ObjectSnapshot<ObjectType>(managedObjectID: self.managedObjectID, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func asSnapshot(in transaction: BaseDataTransaction) -> ObjectSnapshot<ObjectType>? {
|
||||||
|
|
||||||
|
let context = transaction.unsafeContext()
|
||||||
|
return ObjectSnapshot<ObjectType>(managedObjectID: self.managedObjectID, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Internal
|
||||||
|
|
||||||
|
internal let managedObjectID: NSManagedObjectID
|
||||||
|
|
||||||
|
internal var entityIdentifier: Internals.EntityIdentifier {
|
||||||
|
|
||||||
|
return Internals.EntityIdentifier(self.managedObjectID.entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ import Foundation
|
|||||||
- `UnsafeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
|
- `UnsafeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
|
||||||
- `CoreStoreSchema`: describes models written for `CoreStoreObject` Swift class declarations.
|
- `CoreStoreSchema`: describes models written for `CoreStoreObject` Swift class declarations.
|
||||||
*/
|
*/
|
||||||
public protocol DynamicSchema {
|
public protocol DynamicSchema: Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The version string for this model schema.
|
The version string for this model schema.
|
||||||
@@ -45,5 +45,6 @@ public protocol DynamicSchema {
|
|||||||
/**
|
/**
|
||||||
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
|
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
func rawModel() -> NSManagedObjectModel
|
func rawModel() -> NSManagedObjectModel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,31 +203,37 @@ public /*abstract*/ class DynamicEntity: Hashable {
|
|||||||
/**
|
/**
|
||||||
Do not use directly.
|
Do not use directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public let type: DynamicObject.Type
|
public let type: DynamicObject.Type
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Do not use directly.
|
Do not use directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public let entityName: EntityName
|
public let entityName: EntityName
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Do not use directly.
|
Do not use directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public let isAbstract: Bool
|
public let isAbstract: Bool
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Do not use directly.
|
Do not use directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public let versionHashModifier: String?
|
public let versionHashModifier: String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Do not use directly.
|
Do not use directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public let indexes: [[KeyPathString]]
|
public let indexes: [[KeyPathString]]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Do not use directly.
|
Do not use directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public let uniqueConstraints: [[KeyPathString]]
|
public let uniqueConstraints: [[KeyPathString]]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public protocol FieldRelationshipType {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_toReturnType(
|
static func cs_toReturnType(
|
||||||
from value: NativeValueType?
|
from value: NativeValueType?
|
||||||
) -> Self
|
) -> Self
|
||||||
@@ -64,6 +65,7 @@ public protocol FieldRelationshipType {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_toPublishedType(
|
static func cs_toPublishedType(
|
||||||
from value: SnapshotValueType,
|
from value: SnapshotValueType,
|
||||||
in context: NSManagedObjectContext
|
in context: NSManagedObjectContext
|
||||||
@@ -72,6 +74,7 @@ public protocol FieldRelationshipType {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_toNativeType(
|
static func cs_toNativeType(
|
||||||
from value: Self
|
from value: Self
|
||||||
) -> NativeValueType?
|
) -> NativeValueType?
|
||||||
@@ -79,6 +82,7 @@ public protocol FieldRelationshipType {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_toSnapshotType(
|
static func cs_toSnapshotType(
|
||||||
from value: PublishedType
|
from value: PublishedType
|
||||||
) -> SnapshotValueType
|
) -> SnapshotValueType
|
||||||
@@ -86,8 +90,9 @@ public protocol FieldRelationshipType {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
static func cs_valueForSnapshot(
|
static func cs_valueForSnapshot(
|
||||||
from objectIDs: [DestinationObjectType.ObjectID]
|
from objectIDs: [NSManagedObjectID]
|
||||||
) -> SnapshotValueType
|
) -> SnapshotValueType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +143,7 @@ extension Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrap
|
|||||||
in context: NSManagedObjectContext
|
in context: NSManagedObjectContext
|
||||||
) -> PublishedType {
|
) -> PublishedType {
|
||||||
|
|
||||||
return value.map(context.objectPublisher(objectID:))
|
return value.map(context.objectPublisher(managedObjectID:))
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cs_toNativeType(
|
public static func cs_toNativeType(
|
||||||
@@ -152,11 +157,11 @@ extension Optional: FieldRelationshipType, FieldRelationshipToOneType where Wrap
|
|||||||
from value: PublishedType
|
from value: PublishedType
|
||||||
) -> SnapshotValueType {
|
) -> SnapshotValueType {
|
||||||
|
|
||||||
return value?.objectID()
|
return value?.cs_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cs_valueForSnapshot(
|
public static func cs_valueForSnapshot(
|
||||||
from objectIDs: [DestinationObjectType.ObjectID]
|
from objectIDs: [NSManagedObjectID]
|
||||||
) -> SnapshotValueType {
|
) -> SnapshotValueType {
|
||||||
|
|
||||||
return objectIDs.first
|
return objectIDs.first
|
||||||
@@ -194,7 +199,7 @@ extension Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelati
|
|||||||
in context: NSManagedObjectContext
|
in context: NSManagedObjectContext
|
||||||
) -> PublishedType {
|
) -> PublishedType {
|
||||||
|
|
||||||
return value.map(context.objectPublisher(objectID:))
|
return value.map(context.objectPublisher(managedObjectID:))
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cs_toNativeType(
|
public static func cs_toNativeType(
|
||||||
@@ -208,11 +213,11 @@ extension Array: FieldRelationshipType, FieldRelationshipToManyType, FieldRelati
|
|||||||
from value: PublishedType
|
from value: PublishedType
|
||||||
) -> SnapshotValueType {
|
) -> SnapshotValueType {
|
||||||
|
|
||||||
return value.map({ $0.objectID() })
|
return value.map({ $0.cs_id() })
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cs_valueForSnapshot(
|
public static func cs_valueForSnapshot(
|
||||||
from objectIDs: [DestinationObjectType.ObjectID]
|
from objectIDs: [NSManagedObjectID]
|
||||||
) -> SnapshotValueType {
|
) -> SnapshotValueType {
|
||||||
|
|
||||||
return objectIDs
|
return objectIDs
|
||||||
@@ -250,7 +255,7 @@ extension Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelation
|
|||||||
in context: NSManagedObjectContext
|
in context: NSManagedObjectContext
|
||||||
) -> PublishedType {
|
) -> PublishedType {
|
||||||
|
|
||||||
return PublishedType(value.map(context.objectPublisher(objectID:)))
|
return PublishedType(value.map(context.objectPublisher(managedObjectID:)))
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cs_toNativeType(
|
public static func cs_toNativeType(
|
||||||
@@ -264,11 +269,11 @@ extension Set: FieldRelationshipType, FieldRelationshipToManyType, FieldRelation
|
|||||||
from value: PublishedType
|
from value: PublishedType
|
||||||
) -> SnapshotValueType {
|
) -> SnapshotValueType {
|
||||||
|
|
||||||
return SnapshotValueType(value.map({ $0.objectID() }))
|
return SnapshotValueType(value.map({ $0.cs_id() }))
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cs_valueForSnapshot(
|
public static func cs_valueForSnapshot(
|
||||||
from objectIDs: [DestinationObjectType.ObjectID]
|
from objectIDs: [NSManagedObjectID]
|
||||||
) -> SnapshotValueType {
|
) -> SnapshotValueType {
|
||||||
|
|
||||||
return .init(objectIDs)
|
return .init(objectIDs)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public protocol FieldOptionalType: ExpressibleByNilLiteral {
|
|||||||
/**
|
/**
|
||||||
Used internally by CoreStore. Do not call directly.
|
Used internally by CoreStore. Do not call directly.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
var cs_wrappedValue: Wrapped? { get }
|
var cs_wrappedValue: Wrapped? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ extension ForEach where Content: View {
|
|||||||
public init<O: DynamicObject>(
|
public init<O: DynamicObject>(
|
||||||
_ objectSnapshots: Data,
|
_ objectSnapshots: Data,
|
||||||
@ViewBuilder content: @escaping (ObjectSnapshot<O>) -> Content
|
@ViewBuilder content: @escaping (ObjectSnapshot<O>) -> Content
|
||||||
) where Data.Element == ObjectSnapshot<O>, ID == O.ObjectID {
|
) where Data.Element == ObjectSnapshot<O>, ID == NSManagedObjectID {
|
||||||
|
|
||||||
self.init(objectSnapshots, id: \.cs_objectID, content: content)
|
self.init(objectSnapshots, id: \.managedObjectID, content: content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,9 +89,9 @@ extension ForEach where Content: View {
|
|||||||
public init<O: DynamicObject>(
|
public init<O: DynamicObject>(
|
||||||
objectIn listSnapshot: Data,
|
objectIn listSnapshot: Data,
|
||||||
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
||||||
) where Data == ListSnapshot<O>, ID == O.ObjectID {
|
) where Data == ListSnapshot<O>, ID == NSManagedObjectID {
|
||||||
|
|
||||||
self.init(listSnapshot, id: \.cs_objectID, content: content)
|
self.init(listSnapshot, id: \.managedObjectID, content: content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,9 +118,9 @@ extension ForEach where Content: View {
|
|||||||
public init<O: DynamicObject>(
|
public init<O: DynamicObject>(
|
||||||
objectIn objectPublishers: Data,
|
objectIn objectPublishers: Data,
|
||||||
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
||||||
) where Data.Element == ObjectPublisher<O>, ID == O.ObjectID {
|
) where Data.Element == ObjectPublisher<O>, ID == NSManagedObjectID {
|
||||||
|
|
||||||
self.init(objectPublishers, id: \.cs_objectID, content: content)
|
self.init(objectPublishers, id: \.managedObjectID, content: content)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,9 +191,9 @@ extension ForEach where Content: View {
|
|||||||
public init<O: DynamicObject>(
|
public init<O: DynamicObject>(
|
||||||
objectIn sectionInfo: Data,
|
objectIn sectionInfo: Data,
|
||||||
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
|
||||||
) where Data == ListSnapshot<O>.SectionInfo, ID == O.ObjectID {
|
) where Data == ListSnapshot<O>.SectionInfo, ID == NSManagedObjectID {
|
||||||
|
|
||||||
self.init(sectionInfo, id: \.cs_objectID, content: content)
|
self.init(sectionInfo, id: \.managedObjectID, content: content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -148,7 +148,7 @@ extension From {
|
|||||||
- returns: a `FetchChainBuilder` with closure where the `NSFetchRequest` may be configured
|
- returns: a `FetchChainBuilder` with closure where the `NSFetchRequest` may be configured
|
||||||
*/
|
*/
|
||||||
public func tweak(
|
public func tweak(
|
||||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||||
) -> FetchChainBuilder<O> {
|
) -> FetchChainBuilder<O> {
|
||||||
|
|
||||||
return self.fetchChain(appending: Tweak(fetchRequest))
|
return self.fetchChain(appending: Tweak(fetchRequest))
|
||||||
@@ -273,7 +273,7 @@ extension From {
|
|||||||
*/
|
*/
|
||||||
public func sectionBy(
|
public func sectionBy(
|
||||||
_ sectionKeyPath: KeyPathString,
|
_ sectionKeyPath: KeyPathString,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
@@ -348,7 +348,7 @@ extension From where O: NSManagedObject {
|
|||||||
*/
|
*/
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, T>,
|
_ sectionKeyPath: KeyPath<O, T>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -434,7 +434,7 @@ extension From where O: CoreStoreObject {
|
|||||||
*/
|
*/
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
|
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -453,7 +453,7 @@ extension From where O: CoreStoreObject {
|
|||||||
*/
|
*/
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
|
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -472,7 +472,7 @@ extension From where O: CoreStoreObject {
|
|||||||
*/
|
*/
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
|
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -604,7 +604,7 @@ extension FetchChainBuilder {
|
|||||||
- returns: a new `FetchChainBuilder` containing the `Tweak` clause
|
- returns: a new `FetchChainBuilder` containing the `Tweak` clause
|
||||||
*/
|
*/
|
||||||
public func tweak(
|
public func tweak(
|
||||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||||
) -> FetchChainBuilder<O> {
|
) -> FetchChainBuilder<O> {
|
||||||
|
|
||||||
return self.fetchChain(appending: Tweak(fetchRequest))
|
return self.fetchChain(appending: Tweak(fetchRequest))
|
||||||
@@ -795,7 +795,7 @@ extension QueryChainBuilder {
|
|||||||
- returns: a new `QueryChainBuilder` containing the `Tweak` clause
|
- returns: a new `QueryChainBuilder` containing the `Tweak` clause
|
||||||
*/
|
*/
|
||||||
public func tweak(
|
public func tweak(
|
||||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||||
) -> QueryChainBuilder<O, R> {
|
) -> QueryChainBuilder<O, R> {
|
||||||
|
|
||||||
return self.queryChain(appending: Tweak(fetchRequest))
|
return self.queryChain(appending: Tweak(fetchRequest))
|
||||||
@@ -1093,7 +1093,7 @@ extension SectionMonitorChainBuilder {
|
|||||||
- returns: a new `SectionMonitorChainBuilder` containing the `Tweak` clause
|
- returns: a new `SectionMonitorChainBuilder` containing the `Tweak` clause
|
||||||
*/
|
*/
|
||||||
public func tweak(
|
public func tweak(
|
||||||
_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void
|
_ fetchRequest: @escaping @Sendable (NSFetchRequest<NSFetchRequestResult>) -> Void
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionMonitorChain(appending: Tweak(fetchRequest))
|
return self.sectionMonitorChain(appending: Tweak(fetchRequest))
|
||||||
@@ -1232,7 +1232,7 @@ extension From where O: CoreStoreObject {
|
|||||||
|
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
|
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -1243,7 +1243,7 @@ extension From where O: CoreStoreObject {
|
|||||||
|
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
|
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -1254,7 +1254,7 @@ extension From where O: CoreStoreObject {
|
|||||||
|
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
|
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
@@ -1265,7 +1265,7 @@ extension From where O: CoreStoreObject {
|
|||||||
|
|
||||||
public func sectionBy<T>(
|
public func sectionBy<T>(
|
||||||
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
|
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
|
||||||
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
|
sectionIndexTransformer: @escaping @Sendable (_ sectionName: String?) -> String?
|
||||||
) -> SectionMonitorChainBuilder<O> {
|
) -> SectionMonitorChainBuilder<O> {
|
||||||
|
|
||||||
return self.sectionBy(
|
return self.sectionBy(
|
||||||
|
|||||||
+3
-3
@@ -39,7 +39,7 @@ import CoreData
|
|||||||
let person = transaction.fetchOne(From<Person>("Configuration1"))
|
let person = transaction.fetchOne(From<Person>("Configuration1"))
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public struct From<O: DynamicObject> {
|
public struct From<O: DynamicObject>: Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||||
@@ -162,12 +162,12 @@ public struct From<O: DynamicObject> {
|
|||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
internal let findPersistentStores: (_ context: NSManagedObjectContext) -> [NSPersistentStore]?
|
internal let findPersistentStores: @Sendable (_ context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||||
|
|
||||||
internal init(
|
internal init(
|
||||||
entityClass: O.Type,
|
entityClass: O.Type,
|
||||||
configurations: [ModelConfiguration]?,
|
configurations: [ModelConfiguration]?,
|
||||||
findPersistentStores: @escaping (
|
findPersistentStores: @escaping @Sendable (
|
||||||
_ context: NSManagedObjectContext
|
_ context: NSManagedObjectContext
|
||||||
) -> [NSPersistentStore]?
|
) -> [NSPersistentStore]?
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import CoreData
|
|||||||
/**
|
/**
|
||||||
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
|
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
|
||||||
*/
|
*/
|
||||||
public struct GroupBy<O: DynamicObject>: GroupByClause, QueryClause, Hashable {
|
public struct GroupBy<O: DynamicObject>: GroupByClause, QueryClause, Hashable, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Initializes a `GroupBy` clause with an empty list of key path strings
|
Initializes a `GroupBy` clause with an empty list of key path strings
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import CoreData
|
|||||||
/**
|
/**
|
||||||
A storage interface that is backed only in memory.
|
A storage interface that is backed only in memory.
|
||||||
*/
|
*/
|
||||||
public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
public final class InMemoryStore: StorageInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Initializes an `InMemoryStore` for the specified configuration
|
Initializes an `InMemoryStore` for the specified configuration
|
||||||
@@ -74,6 +74,7 @@ public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
|||||||
/**
|
/**
|
||||||
Do not call directly. Used by the `DataStack` internally.
|
Do not call directly. Used by the `DataStack` internally.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public func cs_didAddToDataStack(_ dataStack: DataStack) {
|
public func cs_didAddToDataStack(_ dataStack: DataStack) {
|
||||||
|
|
||||||
self.dataStack = dataStack
|
self.dataStack = dataStack
|
||||||
@@ -82,6 +83,7 @@ public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
|||||||
/**
|
/**
|
||||||
Do not call directly. Used by the `DataStack` internally.
|
Do not call directly. Used by the `DataStack` internally.
|
||||||
*/
|
*/
|
||||||
|
@_spi(Internals)
|
||||||
public func cs_didRemoveFromDataStack(_ dataStack: DataStack) {
|
public func cs_didRemoveFromDataStack(_ dataStack: DataStack) {
|
||||||
|
|
||||||
self.dataStack = nil
|
self.dataStack = nil
|
||||||
@@ -90,5 +92,5 @@ public final class InMemoryStore: StorageInterface, @unchecked Sendable {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private weak var dataStack: DataStack?
|
private nonisolated(unsafe) weak var dataStack: DataStack?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ extension Internals {
|
|||||||
internal typealias Arguments = T
|
internal typealias Arguments = T
|
||||||
internal typealias Result = U
|
internal typealias Result = U
|
||||||
|
|
||||||
internal init(_ closure: @escaping (T) -> U) {
|
internal init(_ closure: @escaping @Sendable (T) -> U) {
|
||||||
|
|
||||||
self.closure = closure
|
self.closure = closure
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,6 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let closure: (T) -> U
|
private let closure: @Sendable (T) -> U
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ import QuartzCore
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - Internals
|
// MARK: - Internals
|
||||||
|
|
||||||
@@ -99,7 +97,7 @@ extension Internals {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let performDiffingUpdates: @MainActor () -> Void = {
|
let performDiffingUpdates: @MainActor @Sendable () -> Void = {
|
||||||
|
|
||||||
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
let changeset = StagedChangeset(source: self.sections, target: newSections)
|
||||||
performUpdates(
|
performUpdates(
|
||||||
@@ -148,7 +146,7 @@ extension Internals {
|
|||||||
return self.sections[section].differenceIdentifier
|
return self.sections[section].differenceIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
|
func itemIdentifier(for indexPath: IndexPath) -> NSManagedObjectID? {
|
||||||
|
|
||||||
guard self.sections.indices.contains(indexPath.section) else {
|
guard self.sections.indices.contains(indexPath.section) else {
|
||||||
|
|
||||||
@@ -162,9 +160,9 @@ extension Internals {
|
|||||||
return items[indexPath.item].differenceIdentifier
|
return items[indexPath.item].differenceIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
|
func indexPath(for itemIdentifier: NSManagedObjectID) -> IndexPath? {
|
||||||
|
|
||||||
let indexPathMap: [O.ObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
|
let indexPathMap: [NSManagedObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
|
||||||
|
|
||||||
for (itemIndex, item) in section.element.elements.enumerated() {
|
for (itemIndex, item) in section.element.elements.enumerated() {
|
||||||
|
|
||||||
@@ -243,7 +241,7 @@ extension Internals {
|
|||||||
|
|
||||||
fileprivate init() {}
|
fileprivate init() {}
|
||||||
|
|
||||||
fileprivate func dispatch(_ action: @escaping @MainActor () -> Void) {
|
fileprivate func dispatch(_ action: @escaping @MainActor @Sendable () -> Void) {
|
||||||
|
|
||||||
let count = self.executingCount.incrementAndGet()
|
let count = self.executingCount.incrementAndGet()
|
||||||
if Thread.isMainThread && count == 1 {
|
if Thread.isMainThread && count == 1 {
|
||||||
@@ -300,7 +298,7 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private let value: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
|
private let value: Internals.Mutex<Int> = .init(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ extension Internals {
|
|||||||
|
|
||||||
// MARK: - EntityIdentifier
|
// MARK: - EntityIdentifier
|
||||||
|
|
||||||
internal struct EntityIdentifier: Hashable {
|
internal struct EntityIdentifier: Hashable, Sendable {
|
||||||
|
|
||||||
// MARK: - Category
|
// MARK: - Category
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// Internals.Mutex.swift
|
||||||
|
// CoreStore
|
||||||
|
//
|
||||||
|
// Copyright © 2026 John Rommel Estropia
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Synchronization
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Internals
|
||||||
|
|
||||||
|
extension Internals {
|
||||||
|
|
||||||
|
// MARK: - Mutex
|
||||||
|
|
||||||
|
internal struct Mutex<Value: ~Copyable>: ~Copyable, @unchecked Sendable {
|
||||||
|
|
||||||
|
// MARK: Internal
|
||||||
|
|
||||||
|
init(_ initialValue: consuming sending Value) {
|
||||||
|
|
||||||
|
self.storage = .init(initialValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
borrowing func withLock<Result, E>(
|
||||||
|
_ body: (inout sending Value) throws(E) -> sending Result
|
||||||
|
) throws(E) -> sending Result
|
||||||
|
where E: Error, Result: ~Copyable {
|
||||||
|
|
||||||
|
let storage = self.storage
|
||||||
|
storage.lock()
|
||||||
|
defer {
|
||||||
|
|
||||||
|
storage.unlock()
|
||||||
|
}
|
||||||
|
return try body(&storage.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
borrowing func withLockUnchecked<Result, E>(
|
||||||
|
_ body: (inout sending Value) throws(E) -> Result
|
||||||
|
) throws(E) -> sending Result
|
||||||
|
where E: Error {
|
||||||
|
|
||||||
|
let storage = self.storage
|
||||||
|
storage.lock()
|
||||||
|
defer {
|
||||||
|
|
||||||
|
storage.unlock()
|
||||||
|
}
|
||||||
|
return try body(&storage.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
private let storage: Storage
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - Storage
|
||||||
|
|
||||||
|
fileprivate final class Storage {
|
||||||
|
|
||||||
|
// MARK: FilePrivate
|
||||||
|
|
||||||
|
var value: Value
|
||||||
|
|
||||||
|
init(_ initialValue: consuming sending Value) {
|
||||||
|
|
||||||
|
self.unfairLock = .allocate(capacity: 1)
|
||||||
|
self.unfairLock.initialize(to: os_unfair_lock())
|
||||||
|
self.value = initialValue
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
|
||||||
|
self.unfairLock.deinitialize(count: 1)
|
||||||
|
self.unfairLock.deallocate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func lock() {
|
||||||
|
os_unfair_lock_lock(self.unfairLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlock() {
|
||||||
|
os_unfair_lock_unlock(self.unfairLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Private
|
||||||
|
|
||||||
|
private let unfairLock: os_unfair_lock_t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,27 @@ extension Internals {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init(
|
||||||
|
notificationName: Notification.Name,
|
||||||
|
object: Any?,
|
||||||
|
closure: @escaping @MainActor (_ note: Notification) -> Void
|
||||||
|
) {
|
||||||
|
|
||||||
|
self.observer = NotificationCenter.default.addObserver(
|
||||||
|
forName: notificationName,
|
||||||
|
object: object,
|
||||||
|
queue: .main,
|
||||||
|
using: { note in
|
||||||
|
|
||||||
|
nonisolated(unsafe) let note = note
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
|
closure(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
||||||
NotificationCenter.default.removeObserver(self.observer)
|
NotificationCenter.default.removeObserver(self.observer)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ extension Internals {
|
|||||||
|
|
||||||
internal func addObserver<U: AnyObject>(
|
internal func addObserver<U: AnyObject>(
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
closure: @escaping (T) -> Void
|
closure: @escaping @Sendable (T) -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.observers.setObject(
|
self.observers.setObject(
|
||||||
|
|||||||
+14
-10
@@ -139,15 +139,6 @@ internal enum Internals {
|
|||||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@inline(__always)
|
|
||||||
internal static func autoreleasepool<T, E>(
|
|
||||||
_ closure: () throws(E) -> T
|
|
||||||
) throws(E) -> T {
|
|
||||||
|
|
||||||
return try ObjectiveC.autoreleasepool(invoking: closure)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func withCheckedThrowingContinuation<T>(
|
internal static func withCheckedThrowingContinuation<T>(
|
||||||
function: String = #function,
|
function: String = #function,
|
||||||
@@ -160,6 +151,17 @@ internal enum Internals {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if compiler(>=27)
|
||||||
|
|
||||||
|
@inline(__always)
|
||||||
|
internal static func autoreleasepool<T, E>(
|
||||||
|
_ closure: () throws(E) -> T
|
||||||
|
) throws(E) -> T {
|
||||||
|
|
||||||
|
return try ObjectiveC.autoreleasepool(invoking: closure)
|
||||||
|
}
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func withCheckedThrowingContinuation<T, E>(
|
internal static func withCheckedThrowingContinuation<T, E>(
|
||||||
function: String = #function,
|
function: String = #function,
|
||||||
@@ -172,10 +174,12 @@ internal enum Internals {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
internal static func mainActorImmediate(
|
internal static func mainActorImmediate(
|
||||||
_ body: @escaping @MainActor () -> Void
|
_ body: @escaping @MainActor @Sendable () -> Void
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if #available(iOS 26.0, macOS 26.0, watchOS 26.0, tvOS 26.0, *) {
|
if #available(iOS 26.0, macOS 26.0, watchOS 26.0, tvOS 26.0, *) {
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ import CoreData
|
|||||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
public struct Into<O: DynamicObject>: Hashable, @unchecked Sendable {
|
public struct Into<O: DynamicObject>: Hashable, Sendable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
The associated `NSManagedObject` or `CoreStoreObject` entity class
|
||||||
|
|||||||
@@ -836,7 +836,7 @@ public func == <O, D: FieldRelationshipToOneType, R: ObjectRepresentation>(
|
|||||||
|
|
||||||
return Where<O>(
|
return Where<O>(
|
||||||
O.meta[keyPath: keyPath].keyPath,
|
O.meta[keyPath: keyPath].keyPath,
|
||||||
isEqualTo: object?.objectID()
|
isEqualTo: object?.cs_id()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,7 +870,7 @@ public func != <O, D: FieldRelationshipToOneType, R: ObjectRepresentation>(
|
|||||||
|
|
||||||
return !Where<O>(
|
return !Where<O>(
|
||||||
O.meta[keyPath: keyPath].keyPath,
|
O.meta[keyPath: keyPath].keyPath,
|
||||||
isEqualTo: object?.objectID()
|
isEqualTo: object?.cs_id()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+75
-55
@@ -66,7 +66,7 @@ import CoreData
|
|||||||
```
|
```
|
||||||
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
||||||
*/
|
*/
|
||||||
public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable {
|
public final class ListMonitor<O: DynamicObject>: Hashable, Sendable {
|
||||||
|
|
||||||
// MARK: Public (Accessors)
|
// MARK: Public (Accessors)
|
||||||
|
|
||||||
@@ -386,8 +386,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
- parameter observer: a `ListObserver` to send change notifications to
|
- parameter observer: a `ListObserver` to send change notifications to
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == O {
|
public func addObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == O {
|
||||||
|
|
||||||
|
let managedObjectContext = self.managedObjectContext
|
||||||
self.unregisterObserver(observer)
|
self.unregisterObserver(observer)
|
||||||
self.registerObserver(
|
self.registerObserver(
|
||||||
observer,
|
observer,
|
||||||
@@ -395,28 +397,28 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
observer.listMonitorWillChange(
|
observer.listMonitorWillChange(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didChange: { (observer, monitor) in
|
didChange: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorDidChange(
|
observer.listMonitorDidChange(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
willRefetch: { (observer, monitor) in
|
willRefetch: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorWillRefetch(
|
observer.listMonitorWillRefetch(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didRefetch: { (observer, monitor) in
|
didRefetch: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorDidRefetch(
|
observer.listMonitorDidRefetch(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -433,8 +435,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
- parameter observer: a `ListObjectObserver` to send change notifications to
|
- parameter observer: a `ListObjectObserver` to send change notifications to
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<U: ListObjectObserver>(_ observer: U) where U.ListEntityType == O {
|
public func addObserver<U: ListObjectObserver>(_ observer: U) where U.ListEntityType == O {
|
||||||
|
|
||||||
|
let managedObjectContext = self.managedObjectContext
|
||||||
self.unregisterObserver(observer)
|
self.unregisterObserver(observer)
|
||||||
self.registerObserver(
|
self.registerObserver(
|
||||||
observer,
|
observer,
|
||||||
@@ -442,28 +446,28 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
observer.listMonitorWillChange(
|
observer.listMonitorWillChange(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didChange: { (observer, monitor) in
|
didChange: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorDidChange(
|
observer.listMonitorDidChange(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
willRefetch: { (observer, monitor) in
|
willRefetch: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorWillRefetch(
|
observer.listMonitorWillRefetch(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didRefetch: { (observer, monitor) in
|
didRefetch: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorDidRefetch(
|
observer.listMonitorDidRefetch(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -475,7 +479,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didInsertObject: object,
|
didInsertObject: object,
|
||||||
toIndexPath: toIndexPath,
|
toIndexPath: toIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
||||||
@@ -484,7 +488,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didDeleteObject: object,
|
didDeleteObject: object,
|
||||||
fromIndexPath: fromIndexPath,
|
fromIndexPath: fromIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
||||||
@@ -493,7 +497,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didUpdateObject: object,
|
didUpdateObject: object,
|
||||||
atIndexPath: atIndexPath,
|
atIndexPath: atIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
||||||
@@ -503,7 +507,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
didMoveObject: object,
|
didMoveObject: object,
|
||||||
fromIndexPath: fromIndexPath,
|
fromIndexPath: fromIndexPath,
|
||||||
toIndexPath: toIndexPath,
|
toIndexPath: toIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -520,8 +524,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
- parameter observer: a `ListSectionObserver` to send change notifications to
|
- parameter observer: a `ListSectionObserver` to send change notifications to
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func addObserver<U: ListSectionObserver>(_ observer: U) where U.ListEntityType == O {
|
public func addObserver<U: ListSectionObserver>(_ observer: U) where U.ListEntityType == O {
|
||||||
|
|
||||||
|
let managedObjectContext = self.managedObjectContext
|
||||||
self.unregisterObserver(observer)
|
self.unregisterObserver(observer)
|
||||||
self.registerObserver(
|
self.registerObserver(
|
||||||
observer,
|
observer,
|
||||||
@@ -529,28 +535,28 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
observer.listMonitorWillChange(
|
observer.listMonitorWillChange(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didChange: { (observer, monitor) in
|
didChange: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorDidChange(
|
observer.listMonitorDidChange(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
willRefetch: { (observer, monitor) in
|
willRefetch: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorWillRefetch(
|
observer.listMonitorWillRefetch(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didRefetch: { (observer, monitor) in
|
didRefetch: { (observer, monitor) in
|
||||||
|
|
||||||
observer.listMonitorDidRefetch(
|
observer.listMonitorDidRefetch(
|
||||||
monitor,
|
monitor,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -562,7 +568,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didInsertObject: object,
|
didInsertObject: object,
|
||||||
toIndexPath: toIndexPath,
|
toIndexPath: toIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
|
||||||
@@ -571,7 +577,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didDeleteObject: object,
|
didDeleteObject: object,
|
||||||
fromIndexPath: fromIndexPath,
|
fromIndexPath: fromIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
didUpdateObject: { (observer, monitor, object, atIndexPath) in
|
||||||
@@ -580,7 +586,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didUpdateObject: object,
|
didUpdateObject: object,
|
||||||
atIndexPath: atIndexPath,
|
atIndexPath: atIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
|
||||||
@@ -590,7 +596,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
didMoveObject: object,
|
didMoveObject: object,
|
||||||
fromIndexPath: fromIndexPath,
|
fromIndexPath: fromIndexPath,
|
||||||
toIndexPath: toIndexPath,
|
toIndexPath: toIndexPath,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -602,7 +608,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didInsertSection: sectionInfo,
|
didInsertSection: sectionInfo,
|
||||||
toSectionIndex: toIndex,
|
toSectionIndex: toIndex,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
didDeleteSection: { (observer, monitor, sectionInfo, fromIndex) in
|
didDeleteSection: { (observer, monitor, sectionInfo, fromIndex) in
|
||||||
@@ -611,7 +617,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
monitor,
|
monitor,
|
||||||
didDeleteSection: sectionInfo,
|
didDeleteSection: sectionInfo,
|
||||||
fromSectionIndex: fromIndex,
|
fromSectionIndex: fromIndex,
|
||||||
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
|
sourceIdentifier: managedObjectContext.saveMetadata?.sourceIdentifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -624,6 +630,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
- parameter observer: a `ListObserver` to unregister notifications to
|
- parameter observer: a `ListObserver` to unregister notifications to
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func removeObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == O {
|
public func removeObserver<U: ListObserver>(_ observer: U) where U.ListEntityType == O {
|
||||||
|
|
||||||
self.unregisterObserver(observer)
|
self.unregisterObserver(observer)
|
||||||
@@ -635,7 +642,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
/**
|
/**
|
||||||
Returns `true` if a call to `refetch(...)` was made to the `ListMonitor` and is currently waiting for the fetching to complete. Returns `false` otherwise.
|
Returns `true` if a call to `refetch(...)` was made to the `ListMonitor` and is currently waiting for the fetching to complete. Returns `false` otherwise.
|
||||||
*/
|
*/
|
||||||
public private(set) var isPendingRefetch = false
|
public nonisolated(unsafe) private(set) var isPendingRefetch = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Asks the `ListMonitor` to refetch its objects using the specified series of `FetchClause`s. Note that this method does not execute the fetch immediately; the actual fetching will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
Asks the `ListMonitor` to refetch its objects using the specified series of `FetchClause`s. Note that this method does not execute the fetch immediately; the actual fetching will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
||||||
@@ -646,6 +653,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||||
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func refetch(
|
public func refetch(
|
||||||
_ fetchClauses: FetchClause...,
|
_ fetchClauses: FetchClause...,
|
||||||
sourceIdentifier: (any Sendable)? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
@@ -666,6 +674,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
|
||||||
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
|
||||||
*/
|
*/
|
||||||
|
@MainActor
|
||||||
public func refetch(
|
public func refetch(
|
||||||
_ fetchClauses: [FetchClause],
|
_ fetchClauses: [FetchClause],
|
||||||
sourceIdentifier: (any Sendable)? = nil
|
sourceIdentifier: (any Sendable)? = nil
|
||||||
@@ -892,6 +901,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
internal func registerObserver<U: AnyObject & Sendable>(
|
internal func registerObserver<U: AnyObject & Sendable>(
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
willChange: @escaping @Sendable (
|
willChange: @escaping @Sendable (
|
||||||
@@ -969,6 +979,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
internal func registerObserver<U: AnyObject & Sendable>(
|
internal func registerObserver<U: AnyObject & Sendable>(
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
didInsertObject: @escaping @Sendable (
|
didInsertObject: @escaping @Sendable (
|
||||||
@@ -1056,6 +1067,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
internal func registerObserver<U: AnyObject & Sendable>(
|
internal func registerObserver<U: AnyObject & Sendable>(
|
||||||
_ observer: U,
|
_ observer: U,
|
||||||
didInsertSection: @escaping @Sendable (
|
didInsertSection: @escaping @Sendable (
|
||||||
@@ -1104,6 +1116,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
internal func unregisterObserver(_ observer: AnyObject) {
|
internal func unregisterObserver(_ observer: AnyObject) {
|
||||||
|
|
||||||
Internals.assert(
|
Internals.assert(
|
||||||
@@ -1125,6 +1138,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
Internals.setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer)
|
Internals.setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
internal func refetch(
|
internal func refetch(
|
||||||
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
sourceIdentifier: (any Sendable)?
|
sourceIdentifier: (any Sendable)?
|
||||||
@@ -1154,7 +1168,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||||
context: self.fetchedResultsController.managedObjectContext,
|
context: self.managedObjectContext,
|
||||||
from: self.from,
|
from: self.from,
|
||||||
sectionBy: self.sectionBy,
|
sectionBy: self.sectionBy,
|
||||||
applyFetchClauses: self.applyFetchClauses
|
applyFetchClauses: self.applyFetchClauses
|
||||||
@@ -1216,32 +1230,34 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
fileprivate var fetchedResultsController: Internals.CoreStoreFetchedResultsController
|
private let managedObjectContext: NSManagedObjectContext
|
||||||
fileprivate let taskGroup = DispatchGroup()
|
private let taskGroup = DispatchGroup()
|
||||||
internal let sectionByIndexTransformer: (_ sectionName: KeyPathString?) -> String?
|
private let sectionByIndexTransformer: @Sendable (_ sectionName: KeyPathString?) -> String?
|
||||||
|
|
||||||
private let isSectioned: Bool
|
private let isSectioned: Bool
|
||||||
|
|
||||||
private var willChangeListKey: Void?
|
|
||||||
private var didChangeListKey: Void?
|
|
||||||
private var willRefetchListKey: Void?
|
|
||||||
private var didRefetchListKey: Void?
|
|
||||||
|
|
||||||
private var didInsertObjectKey: Void?
|
|
||||||
private var didDeleteObjectKey: Void?
|
|
||||||
private var didUpdateObjectKey: Void?
|
|
||||||
private var didMoveObjectKey: Void?
|
|
||||||
|
|
||||||
private var didInsertSectionKey: Void?
|
|
||||||
private var didDeleteSectionKey: Void?
|
|
||||||
|
|
||||||
private var fetchedResultsControllerDelegate: Internals.FetchedResultsControllerDelegate
|
|
||||||
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
|
|
||||||
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
|
|
||||||
private let transactionQueue: DispatchQueue
|
private let transactionQueue: DispatchQueue
|
||||||
private var applyFetchClauses: (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
|
||||||
|
|
||||||
private var isPersistentStoreChanging: Bool = false {
|
private nonisolated(unsafe) var applyFetchClauses: (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
|
||||||
|
|
||||||
|
private nonisolated(unsafe) var willChangeListKey: Void?
|
||||||
|
private nonisolated(unsafe) var didChangeListKey: Void?
|
||||||
|
private nonisolated(unsafe) var willRefetchListKey: Void?
|
||||||
|
private nonisolated(unsafe) var didRefetchListKey: Void?
|
||||||
|
|
||||||
|
private nonisolated(unsafe) var didInsertObjectKey: Void?
|
||||||
|
private nonisolated(unsafe) var didDeleteObjectKey: Void?
|
||||||
|
private nonisolated(unsafe) var didUpdateObjectKey: Void?
|
||||||
|
private nonisolated(unsafe) var didMoveObjectKey: Void?
|
||||||
|
|
||||||
|
private nonisolated(unsafe) var didInsertSectionKey: Void?
|
||||||
|
private nonisolated(unsafe) var didDeleteSectionKey: Void?
|
||||||
|
|
||||||
|
private nonisolated(unsafe) var observerForWillChangePersistentStore: Internals.NotificationObserver!
|
||||||
|
private nonisolated(unsafe) var observerForDidChangePersistentStore: Internals.NotificationObserver!
|
||||||
|
|
||||||
|
private nonisolated(unsafe) var fetchedResultsController: Internals.CoreStoreFetchedResultsController
|
||||||
|
private nonisolated(unsafe) var fetchedResultsControllerDelegate: Internals.FetchedResultsControllerDelegate
|
||||||
|
|
||||||
|
private nonisolated(unsafe) var isPersistentStoreChanging: Bool = false {
|
||||||
|
|
||||||
didSet {
|
didSet {
|
||||||
|
|
||||||
@@ -1302,18 +1318,21 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
from: From<O>,
|
from: From<O>,
|
||||||
sectionBy: SectionBy<O>?,
|
sectionBy: SectionBy<O>?,
|
||||||
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
|
||||||
createAsynchronously: (@Sendable (ListMonitor<O>) -> Void)?
|
createAsynchronously: (@MainActor (ListMonitor<O>) -> Void)?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
self.managedObjectContext = context
|
||||||
self.isSectioned = (sectionBy != nil)
|
self.isSectioned = (sectionBy != nil)
|
||||||
self.from = from
|
self.from = from
|
||||||
self.sectionBy = sectionBy
|
self.sectionBy = sectionBy
|
||||||
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
let (fetchedResultsController, fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
|
||||||
context: context,
|
context: context,
|
||||||
from: from,
|
from: from,
|
||||||
sectionBy: sectionBy,
|
sectionBy: sectionBy,
|
||||||
applyFetchClauses: applyFetchClauses
|
applyFetchClauses: applyFetchClauses
|
||||||
)
|
)
|
||||||
|
self.fetchedResultsController = fetchedResultsController
|
||||||
|
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||||
|
|
||||||
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
||||||
|
|
||||||
@@ -1325,7 +1344,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
}
|
}
|
||||||
self.transactionQueue = transactionQueue
|
self.transactionQueue = transactionQueue
|
||||||
self.applyFetchClauses = applyFetchClauses
|
self.applyFetchClauses = applyFetchClauses
|
||||||
self.fetchedResultsControllerDelegate.handler = self
|
fetchedResultsControllerDelegate.handler = self
|
||||||
|
|
||||||
guard let coordinator = context.parentStack?.coordinator else {
|
guard let coordinator = context.parentStack?.coordinator else {
|
||||||
|
|
||||||
@@ -1335,8 +1354,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
self.observerForWillChangePersistentStore = Internals.NotificationObserver(
|
self.observerForWillChangePersistentStore = Internals.NotificationObserver(
|
||||||
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresWillChange,
|
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresWillChange,
|
||||||
object: coordinator,
|
object: coordinator,
|
||||||
queue: OperationQueue.main,
|
closure: { @MainActor [weak self] (note) -> Void in
|
||||||
closure: { [weak self] (note) -> Void in
|
|
||||||
|
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
|
|
||||||
@@ -1357,8 +1375,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
self.observerForDidChangePersistentStore = Internals.NotificationObserver(
|
self.observerForDidChangePersistentStore = Internals.NotificationObserver(
|
||||||
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresDidChange,
|
notificationName: NSNotification.Name.NSPersistentStoreCoordinatorStoresDidChange,
|
||||||
object: coordinator,
|
object: coordinator,
|
||||||
queue: OperationQueue.main,
|
closure: { @MainActor [weak self] (note) -> Void in
|
||||||
closure: { [weak self] (note) -> Void in
|
|
||||||
|
|
||||||
guard let self = self else {
|
guard let self = self else {
|
||||||
|
|
||||||
@@ -1389,10 +1406,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable, @unchecked Sendable
|
|||||||
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
||||||
self.taskGroup.notify(queue: .main) {
|
self.taskGroup.notify(queue: .main) {
|
||||||
|
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
|
||||||
createAsynchronously(self)
|
createAsynchronously(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user