diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26a544c4..cbe67147 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: name: macOS strategy: matrix: - xcode: ["26.6"] + xcode: ["26.2"] config: ["debug", "release"] runs-on: macos-26 steps: @@ -32,7 +32,7 @@ jobs: name: Examples strategy: matrix: - xcode: ["26.6"] + xcode: ["26.2"] config: ["debug"] scheme: ["Reminders", "CaseStudies", "SyncUps"] runs-on: macos-26 diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 5dc5bdb5..0040127d 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -1100,7 +1100,6 @@ isa = XCLocalSwiftPackageReference; relativePath = ..; traits = ( - CasePaths, LazyInitializableByDefault, ); }; @@ -1122,6 +1121,9 @@ kind = upToNextMajorVersion; minimumVersion = 1.7.0; }; + traits = ( + Clocks, + ); }; DCBE8A122D4842BF0071F499 /* XCRemoteSwiftPackageReference "swift-case-paths" */ = { isa = XCRemoteSwiftPackageReference; @@ -1138,6 +1140,8 @@ kind = upToNextMajorVersion; minimumVersion = 2.2.3; }; + traits = ( + ); }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d2dfd2ae..ce3dfb78 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "c133bf7d10c8ce1e5d6506c3d2f080eac8b4c8c2827044d53a9b925e903564fd", + "originHash" : "c56e7b70de4fe8bcc798354797a8405c0eeb2e9bc68bc0f202c14a8b11a0a97f", "pins" : [ { "identity" : "combine-schedulers", @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift", "state" : { - "revision" : "b83108d10f42680d78f23fe4d4d80fc88dab3212", - "version" : "7.11.1" + "revision" : "9ed8c8457e00ff9c7aedb3bf213f20a2cfdf509e", + "version" : "7.11.0" } }, { @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "1197e80bc7e4b177051b6869ef93d8ac3ad677da", - "version" : "1.8.0" + "revision" : "206cbce3882b4de9aee19ce62ac5b7306cadd45b", + "version" : "1.7.3" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "a8cd6c976f335ed361dcecddb0dc39ebda51bc3e", - "version" : "1.6.1" + "revision" : "b9b59eb58c946236d6f16305c576ad194c36444e", + "version" : "1.6.0" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "8dc1fbf2f6255a73dec53b4648164884898db4c5", - "version" : "1.14.1" + "revision" : "16f7dd14ee28d04617090f2a73198b8b316ffa12", + "version" : "1.13.1" } }, { @@ -87,8 +87,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-navigation", "state" : { - "revision" : "fad75807c596fecd724b0fc81cd61c94008faad4", - "version" : "2.10.3" + "revision" : "32f35241b8be0719c4c7f00eb27713b1cadb6248", + "version" : "2.8.0" } }, { @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-perception", "state" : { - "revision" : "de219a1cf34e958134e75a9ebb134cf09bf52fc6", - "version" : "2.0.11" + "revision" : "25ac73741c3436605d61eceb5207e896973918e7", + "version" : "2.0.10" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "8244fe63bf43e58188ab13851ad693eecf6a9e90", - "version" : "2.9.1" + "revision" : "e47a2f545bafa3c0c702600f3e6ce02b3d566b6f", + "version" : "2.8.2" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", "state" : { - "revision" : "1bc16f430d8410e7f087d4c787767b26fd32fe30", - "version" : "1.19.3" + "revision" : "ad5e3190cc63dc288f28546f9c6827efc1e9d495", + "version" : "1.19.2" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "e61b3713460507ce93ed4ca4d8f9e4423bf342ca", - "version" : "0.33.0" + "revision" : "50a429884d7a6c0613df2a31a24009cc436bfb18", + "version" : "0.33.2" } }, { @@ -141,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "8f6abcf4c8950e2679d5b2fee4ca284fd7c34886", - "version" : "1.11.0" + "revision" : "cb281f343fd953280336dcbd3822cdf47c182f5b", + "version" : "1.10.0" } } ], diff --git a/Package.resolved b/Package.resolved index d4587278..2c8e5093 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift", "state" : { - "revision" : "b83108d10f42680d78f23fe4d4d80fc88dab3212", - "version" : "7.11.1" + "revision" : "9ed8c8457e00ff9c7aedb3bf213f20a2cfdf509e", + "version" : "7.11.0" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump", "state" : { - "revision" : "a8cd6c976f335ed361dcecddb0dc39ebda51bc3e", - "version" : "1.6.1" + "revision" : "b9b59eb58c946236d6f16305c576ad194c36444e", + "version" : "1.6.0" } }, { @@ -60,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-dependencies", "state" : { - "revision" : "8dc1fbf2f6255a73dec53b4648164884898db4c5", - "version" : "1.14.1" + "revision" : "f80552807ec92f72fe3fe4543d71879182b0bfd5", + "version" : "1.13.0" } }, { @@ -105,8 +105,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-sharing", "state" : { - "revision" : "8244fe63bf43e58188ab13851ad693eecf6a9e90", - "version" : "2.9.1" + "revision" : "e47a2f545bafa3c0c702600f3e6ce02b3d566b6f", + "version" : "2.8.2" } }, { @@ -114,8 +114,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "1bc16f430d8410e7f087d4c787767b26fd32fe30", - "version" : "1.19.3" + "revision" : "ad5e3190cc63dc288f28546f9c6827efc1e9d495", + "version" : "1.19.2" } }, { @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "9c2935e47b0ed9627e4278c566e0ac386be5472e", - "version" : "0.33.3" + "revision" : "50a429884d7a6c0613df2a31a24009cc436bfb18", + "version" : "0.33.2" } }, { @@ -141,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "8f6abcf4c8950e2679d5b2fee4ca284fd7c34886", - "version" : "1.11.0" + "revision" : "cb281f343fd953280336dcbd3822cdf47c182f5b", + "version" : "1.10.0" } } ], diff --git a/Package.swift b/Package.swift index 317db099..eb33cad5 100644 --- a/Package.swift +++ b/Package.swift @@ -68,6 +68,7 @@ let package = Package( .target( name: "SQLiteData", dependencies: [ + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "GRDB", package: "GRDB.swift"), .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), @@ -86,6 +87,7 @@ let package = Package( name: "SQLiteDataTestSupport", dependencies: [ "SQLiteData", + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "ConcurrencyExtrasTestSupport", package: "swift-concurrency-extras"), .product(name: "CustomDump", package: "swift-custom-dump"), .product(name: "Dependencies", package: "swift-dependencies"), diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 19a896ec..66138923 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -35,6 +35,7 @@ let package = Package( .target( name: "SQLiteData", dependencies: [ + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "GRDB", package: "GRDB.swift"), .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), @@ -47,6 +48,7 @@ let package = Package( name: "SQLiteDataTestSupport", dependencies: [ "SQLiteData", + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "CustomDump", package: "swift-custom-dump"), .product(name: "Dependencies", package: "swift-dependencies"), .product(name: "InlineSnapshotTesting", package: "swift-snapshot-testing"), diff --git a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift index a6c497dd..8451fa7e 100644 --- a/Sources/SQLiteData/CloudKit/CloudKitSharing.swift +++ b/Sources/SQLiteData/CloudKit/CloudKitSharing.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) public import CloudKit + import ConcurrencyExtras import GRDB import IssueReporting public import StructuredQueries @@ -240,7 +241,7 @@ } func unshare(share: CKShare) async throws { - let result = try await syncEngines.withLock(\.private)?.database.modifyRecords( + let result = try await syncEngines.private?.database.modifyRecords( saving: [], deleting: [share.recordID] ) diff --git a/Sources/SQLiteData/CloudKit/Internal/DataManager.swift b/Sources/SQLiteData/CloudKit/Internal/DataManager.swift index 78c6b6b0..9a67b42e 100644 --- a/Sources/SQLiteData/CloudKit/Internal/DataManager.swift +++ b/Sources/SQLiteData/CloudKit/Internal/DataManager.swift @@ -1,4 +1,5 @@ #if canImport(CloudKit) && canImport(CryptoKit) + package import ConcurrencyExtras import CryptoKit import Dependencies package import Foundation @@ -54,7 +55,7 @@ package init() {} package func load(_ url: URL) throws -> Data { - try storage.withLock { storage in + try storage.withValue { storage in guard let data = storage[url] else { struct FileNotFound: Error {} @@ -65,11 +66,11 @@ } package func save(_ data: Data, to url: URL) throws { - storage.withLock { $0[url] = data } + storage.withValue { $0[url] = data } } package func sha256(of fileURL: URL) -> Data? { - storage.withLock { + storage.withValue { $0[fileURL].map { Data(SHA256.hash(data: $0)) } diff --git a/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift b/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift index 1ca58c0c..3af32acf 100644 --- a/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift +++ b/Sources/SQLiteData/CloudKit/Internal/MockCloudContainer.swift @@ -1,4 +1,5 @@ #if canImport(CloudKit) + package import ConcurrencyExtras package import CloudKit import Dependencies @@ -22,13 +23,13 @@ guard let containerIdentifier else { return } @Dependency(\.mockCloudContainers) var mockCloudContainers - mockCloudContainers.withLock { storage in + mockCloudContainers.withValue { storage in storage[containerIdentifier] = self } } package func accountStatus() -> CKAccountStatus { - _accountStatus.withLock(\.self) + _accountStatus.withValue(\.self) } package var rawValue: CKContainer { @@ -36,7 +37,7 @@ } package func accountStatus() async throws -> CKAccountStatus { - _accountStatus.withLock { $0 } + _accountStatus.withValue { $0 } } package func shareMetadata( @@ -48,7 +49,7 @@ ? privateCloudDatabase : sharedCloudDatabase - let rootRecord: CKRecord? = database.state.withLock { + let rootRecord: CKRecord? = database.state.withValue { $0.storage[share.recordID.zoneID]?.records.values.first { record in record.share?.recordID == share.recordID } @@ -79,7 +80,7 @@ -> MockCloudContainer { @Dependency(\.mockCloudContainers) var mockCloudContainers - return mockCloudContainers.withLock { storage in + return mockCloudContainers.withValue { storage in let container: MockCloudContainer if let existingContainer = storage[containerIdentifier] { return existingContainer diff --git a/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift b/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift index 51daf55f..510d1f5c 100644 --- a/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift +++ b/Sources/SQLiteData/CloudKit/Internal/MockCloudDatabase.swift @@ -1,4 +1,5 @@ #if canImport(CloudKit) + package import ConcurrencyExtras package import CloudKit import Dependencies import IssueReporting @@ -47,7 +48,7 @@ let accountStatus = container.accountStatus() guard accountStatus == .available else { throw ckError(forAccountStatus: accountStatus) } - let record = try state.withLock { state in + let record = try state.withValue { state in guard let zone = state.storage[recordID.zoneID] else { throw CKError(.zoneNotFound) } guard let record = zone.records[recordID] @@ -57,7 +58,7 @@ return record } - try state.withLock { state in + try state.withValue { state in for key in record.allKeys() { guard let assetData = state.assets[AssetID(recordID: record.recordID, key: key)] else { continue } @@ -106,7 +107,7 @@ throw CKError(.limitExceeded) } - return state.withLock { state in + return state.withValue { state in let previousStorage = state.storage var saveResults: [CKRecord.ID: Result] = [:] var deleteResults: [CKRecord.ID: Result] = [:] @@ -366,7 +367,7 @@ guard accountStatus == .available else { throw ckError(forAccountStatus: accountStatus) } - return state.withLock { state in + return state.withValue { state in var saveResults: [CKRecordZone.ID: Result] = [:] var deleteResults: [CKRecordZone.ID: Result] = [:] diff --git a/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift b/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift index 82c9c476..c8ef0113 100644 --- a/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/Internal/MockSyncEngine.swift @@ -1,4 +1,5 @@ #if canImport(CloudKit) + package import ConcurrencyExtras package import CloudKit import IssueReporting package import OrderedCollections @@ -26,7 +27,7 @@ } package func acceptShare(metadata: ShareMetadata) { - _ = _acceptedShareMetadata.withLock { $0.insert(metadata) } + _ = _acceptedShareMetadata.withValue { $0.insert(metadata) } } package func fetchChanges(_ options: CKSyncEngine.FetchChangesOptions) async throws { @@ -34,16 +35,16 @@ let zoneIDs: [CKRecordZone.ID] switch options.scope { case .all: - zoneIDs = Array(database.state.withLock(\.storage.keys)) + zoneIDs = Array(database.state.storage.keys) case .allExcluding(let excludedZoneIDs): - zoneIDs = Array(Set(database.state.withLock(\.storage.keys)).subtracting(excludedZoneIDs)) + zoneIDs = Array(Set(database.state.storage.keys).subtracting(excludedZoneIDs)) case .zoneIDs(let includedZoneIDs): zoneIDs = includedZoneIDs @unknown default: fatalError() } - modifications = database.state.withLock { state in + modifications = database.state.withValue { state in zoneIDs.reduce(into: [CKRecord]()) { accum, zoneID in @@ -53,12 +54,12 @@ $0._recordChangeTag != nil, "Records stored in database should have their 'recordChangeTag' assigned." ) - return $0._recordChangeTag! > self.state.changeTag.withLock(\.self) + return $0._recordChangeTag! > self.state.changeTag.value } } } - let deletions = database.state.withLock { + let deletions = database.state.withValue { let records = $0.deletedRecords.filter { recordID, _ in zoneIDs.contains(recordID.zoneID) } @@ -71,7 +72,7 @@ guard !modifications.isEmpty || !deletions.isEmpty else { return } - state.changeTag.withLock { changeTag in + state.changeTag.withValue { changeTag in changeTag = modifications.compactMap(\._recordChangeTag).max() ?? changeTag } @@ -160,38 +161,38 @@ } package var pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange] { - _pendingRecordZoneChanges.withLock { Array($0) } + _pendingRecordZoneChanges.withValue { Array($0) } } package var pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange] { - _pendingDatabaseChanges.withLock { Array($0) } + _pendingDatabaseChanges.withValue { Array($0) } } package func removePendingChanges() { - _pendingDatabaseChanges.withLock { $0.removeAll() } - _pendingRecordZoneChanges.withLock { $0.removeAll() } + _pendingDatabaseChanges.withValue { $0.removeAll() } + _pendingRecordZoneChanges.withValue { $0.removeAll() } } package func add(pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange]) { - self._pendingRecordZoneChanges.withLock { + self._pendingRecordZoneChanges.withValue { $0.append(contentsOf: pendingRecordZoneChanges) } } package func remove(pendingRecordZoneChanges: [CKSyncEngine.PendingRecordZoneChange]) { - self._pendingRecordZoneChanges.withLock { + self._pendingRecordZoneChanges.withValue { $0.subtract(pendingRecordZoneChanges) } } package func add(pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange]) { - self._pendingDatabaseChanges.withLock { + self._pendingDatabaseChanges.withValue { $0.append(contentsOf: pendingDatabaseChanges) } } package func remove(pendingDatabaseChanges: [CKSyncEngine.PendingDatabaseChange]) { - self._pendingDatabaseChanges.withLock { + self._pendingDatabaseChanges.withValue { $0.subtract(pendingDatabaseChanges) } } @@ -438,10 +439,10 @@ } package var `private`: MockSyncEngine { - syncEngines.withLock(\.private) as! MockSyncEngine + syncEngines.private as! MockSyncEngine } package var shared: MockSyncEngine { - syncEngines.withLock(\.shared) as! MockSyncEngine + syncEngines.shared as! MockSyncEngine } package func syncEngine(for scope: CKDatabase.Scope) -> MockSyncEngine { diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 42b115d7..ddcb3f32 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) public import CloudKit + package import ConcurrencyExtras import Dependencies public import GRDB public import IssueReporting @@ -281,7 +282,7 @@ ) #if os(iOS) @Dependency(\.defaultNotificationCenter) var defaultNotificationCenter - notificationsObserver.withLock { + notificationsObserver.withValue { $0 = defaultNotificationCenter.addObserver( forName: UIApplication.willResignActiveNotification, object: nil, @@ -290,7 +291,7 @@ _ = Task { @MainActor in let taskIdentifier = UIApplication.shared.beginBackgroundTask() defer { UIApplication.shared.endBackgroundTask(taskIdentifier) } - let (privateSyncEngine, sharedSyncEngine) = syncEngines.withLock { + let (privateSyncEngine, sharedSyncEngine) = syncEngines.withValue { ($0.private, $0.shared) } try await privateSyncEngine?.sendChanges(CKSyncEngine.SendChangesOptions()) @@ -303,7 +304,7 @@ } deinit { - notificationsObserver.withLock { + notificationsObserver.withValue { guard let observer = $0 else { return } NotificationCenter.default.removeObserver(observer) @@ -429,13 +430,13 @@ public func stop() { guard isRunning else { return } #if DEBUG && canImport(DeveloperToolsSupport) - previewTimerTask.withLock { + previewTimerTask.withValue { $0?.cancel() $0 = nil } #endif observationRegistrar.withMutation(of: self, keyPath: \.isRunning) { - syncEngines.withLock { + syncEngines.withValue { $0 = SyncEngines() } } @@ -444,7 +445,7 @@ /// Determines if the sync engine is currently running or not. public var isRunning: Bool { observationRegistrar.access(self, keyPath: \.isRunning) - return syncEngines.withLock { + return syncEngines.withValue { $0.isRunning } } @@ -452,7 +453,7 @@ private func start() throws -> Task { guard !isRunning else { return Task {} } observationRegistrar.withMutation(of: self, keyPath: \.isRunning) { - syncEngines.withLock { + syncEngines.withValue { let (privateSyncEngine, sharedSyncEngine) = defaultSyncEngines(metadatabase, self) $0 = SyncEngines( private: privateSyncEngine, @@ -512,7 +513,7 @@ @Dependency(\.context) var context @Dependency(\.continuousClock) var clock if context == .preview { - previewTimerTask.withLock { + previewTimerTask.withValue { $0?.cancel() $0 = Task { @Sendable [weak self] in await withErrorReporting { @@ -530,7 +531,7 @@ await withErrorReporting(.sqliteDataCloudKitFailure) { guard try await container.accountStatus() == .available else { return } - syncEngines.withLock { + syncEngines.withValue { $0.private?.state.add(pendingDatabaseChanges: [.saveZone(defaultZone)]) } try await uploadRecordsToCloudKit( @@ -544,7 +545,7 @@ try await cacheUserTables(recordTypes: currentRecordTypes) } } - self.startTask.withLock { + self.startTask.withValue { $0?.cancel() $0 = startTask } @@ -562,8 +563,8 @@ public func fetchChanges( _ options: CKSyncEngine.FetchChangesOptions = CKSyncEngine.FetchChangesOptions() ) async throws { - await startTask.withLock(\.self)?.value - let (privateSyncEngine, sharedSyncEngine) = syncEngines.withLock { + await startTask.withValue(\.self)?.value + let (privateSyncEngine, sharedSyncEngine) = syncEngines.withValue { ($0.private, $0.shared) } guard let privateSyncEngine, let sharedSyncEngine @@ -584,8 +585,8 @@ public func sendChanges( _ options: CKSyncEngine.SendChangesOptions = CKSyncEngine.SendChangesOptions() ) async throws { - await startTask.withLock(\.self)?.value - let (privateSyncEngine, sharedSyncEngine) = syncEngines.withLock { + await startTask.withValue(\.self)?.value + let (privateSyncEngine, sharedSyncEngine) = syncEngines.withValue { ($0.private, $0.shared) } guard let privateSyncEngine, let sharedSyncEngine @@ -655,7 +656,7 @@ false } } - syncEngines.withLock { + syncEngines.withValue { $0.private?.state.add(pendingRecordZoneChanges: changesByIsPrivate[true] ?? []) $0.shared?.state.add(pendingRecordZoneChanges: changesByIsPrivate[false] ?? []) } @@ -836,10 +837,10 @@ } return } - let oldSyncEngine = self.syncEngines.withLock { + let oldSyncEngine = self.syncEngines.withValue { oldZoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared } - let syncEngine = self.syncEngines.withLock { + let syncEngine = self.syncEngines.withValue { zoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared } oldSyncEngine?.state.add(pendingRecordZoneChanges: oldChanges) @@ -877,7 +878,7 @@ return } - let syncEngine = self.syncEngines.withLock { + let syncEngine = self.syncEngines.withValue { zoneID.ownerName == CKCurrentUserDefaultName ? $0.private : $0.shared } syncEngine?.state.add(pendingRecordZoneChanges: changes) @@ -891,7 +892,7 @@ } let container = type(of: container).createContainer(identifier: metadata.containerIdentifier) _ = try await container.accept(metadata) - try await syncEngines.withLock(\.shared)?.fetchChanges( + try await syncEngines.shared?.fetchChanges( CKSyncEngine.FetchChangesOptions( scope: .zoneIDs([rootRecordID.zoneID]), operationGroup: nil @@ -923,22 +924,22 @@ private var sendingChangesCount: Int { get { observationRegistrar.access(self, keyPath: \.isSendingChanges) - return activityCounts.withLock(\.sendingChangesCount) + return activityCounts.withValue(\.sendingChangesCount) } set { observationRegistrar.withMutation(of: self, keyPath: \.isSendingChanges) { - activityCounts.withLock { $0.sendingChangesCount = newValue } + activityCounts.withValue { $0.sendingChangesCount = newValue } } } } private var fetchingChangesCount: Int { get { observationRegistrar.access(self, keyPath: \.isFetchingChanges) - return activityCounts.withLock(\.fetchingChangesCount) + return activityCounts.withValue(\.fetchingChangesCount) } set { observationRegistrar.withMutation(of: self, keyPath: \.isFetchingChanges) { - activityCounts.withLock { $0.fetchingChangesCount = newValue } + activityCounts.withValue { $0.fetchingChangesCount = newValue } } } } @@ -1115,7 +1116,7 @@ #if DEBUG let state = LockIsolated(NextRecordZoneChangeBatchLoggingState()) defer { - let state = state.withLock(\.self) + let state = state.withValue(\.self) if let tabularDescription = state.tabularDescription { logger.debug( """ @@ -1152,7 +1153,7 @@ var sentRecord: CKRecord.ID? #if DEBUG defer { - state.withLock { [missingTable, missingRecord, sentRecord] in + state.withValue { [missingTable, missingRecord, sentRecord] in if let missingTable { $0.events.append("⚠️ Missing table") $0.recordTypes.append(metadata.recordType) @@ -1344,7 +1345,7 @@ changeType: CKSyncEngine.Event.AccountChange.ChangeType, syncEngine: any SyncEngineProtocol ) async { - guard syncEngine === syncEngines.withLock(\.private) + guard syncEngine === syncEngines.private else { return } switch changeType { diff --git a/Sources/SQLiteData/Fetch.swift b/Sources/SQLiteData/Fetch.swift index 2a7c5516..ea645873 100644 --- a/Sources/SQLiteData/Fetch.swift +++ b/Sources/SQLiteData/Fetch.swift @@ -1,6 +1,7 @@ public import GRDB public import Sharing import StructuredQueriesCore +public import Sharing #if canImport(Combine) public import Combine @@ -21,32 +22,11 @@ import StructuredQueriesCore @dynamicMemberLookup @propertyWrapper public struct Fetch: Sendable { - #if canImport(SwiftUI) - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) - /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader { - @storageRestrictions(initializes: box, state) - init(initialValue) { - let box = FetchBox(sharedReader: initialValue) - self.box = box - state = SwiftUI.State(wrappedValue: box) - } - get { state.wrappedValue.sharedReader } - nonmutating set { state.wrappedValue.sharedReader = newValue } - } - - private let box: FetchBox - private let state: SwiftUI.State> - private let generation = SwiftUI.State(wrappedValue: 0) - #else - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) - /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader - #endif + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package, + /// a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader /// Data associated with the underlying query. public var wrappedValue: Value { @@ -113,7 +93,6 @@ public struct Fetch: Sendable { database: (any DatabaseReader)? = nil ) { sharedReader = SharedReader(wrappedValue: wrappedValue, .fetch(request, database: database)) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Replaces the wrapped value with data from the given request. @@ -131,19 +110,6 @@ public struct Fetch: Sendable { try await sharedReader.load(.fetch(request, database: database)) return FetchSubscription(sharedReader: sharedReader) } - - #if !canImport(SwiftUI) - @_transparent - #endif - private func setFetchKeyID( - for request: some FetchKeyRequest, - database: (any DatabaseReader)?, - scheduler: (any ValueObservationScheduler & Hashable)? - ) { - #if canImport(SwiftUI) - box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id - #endif - } } extension Fetch { @@ -166,7 +132,6 @@ extension Fetch { wrappedValue: wrappedValue, .fetch(request, database: database, scheduler: scheduler) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Replaces the wrapped value with data from the given request. @@ -204,11 +169,7 @@ extension Fetch: Equatable where Value: Equatable { #if canImport(SwiftUI) extension Fetch: DynamicProperty { public func update() { - let persisted = state.wrappedValue - if persisted !== box { - persisted.update(from: box) - } - persisted.subscribe(generation: generation) + sharedReader.update() } /// Initializes this property with a request associated with the wrapped value. @@ -231,7 +192,6 @@ extension Fetch: Equatable where Value: Equatable { wrappedValue: wrappedValue, .fetch(request, database: database, animation: animation) ) - setFetchKeyID(for: request, database: database, scheduler: .animation(animation)) } /// Replaces the wrapped value with data from the given request. diff --git a/Sources/SQLiteData/FetchAll.swift b/Sources/SQLiteData/FetchAll.swift index e2a9fd3f..ec118cdf 100644 --- a/Sources/SQLiteData/FetchAll.swift +++ b/Sources/SQLiteData/FetchAll.swift @@ -1,6 +1,7 @@ public import GRDB public import Sharing public import StructuredQueriesCore +public import Sharing #if canImport(Combine) public import Combine @@ -21,32 +22,11 @@ public import StructuredQueriesCore @dynamicMemberLookup @propertyWrapper public struct FetchAll: Sendable { - #if canImport(SwiftUI) - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) - /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader<[Element]> { - @storageRestrictions(initializes: box, state) - init(initialValue) { - let box = FetchBox(sharedReader: initialValue) - self.box = box - state = SwiftUI.State(wrappedValue: box) - } - get { state.wrappedValue.sharedReader } - nonmutating set { state.wrappedValue.sharedReader = newValue } - } - - private let box: FetchBox<[Element]> - private let state: SwiftUI.State> - private let generation = SwiftUI.State(wrappedValue: 0) - #else - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) - /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader<[Element]> = SharedReader(value: []) - #endif + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package, + /// a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader<[Element]> = SharedReader(value: []) /// A collection of data associated with the underlying query. public var wrappedValue: [Element] { @@ -159,12 +139,13 @@ public struct FetchAll: Sendable { Element == V.QueryOutput, V.QueryOutput: Sendable { - let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch( + FetchAllStatementValueRequest(statement: statement), + database: database + ) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -183,12 +164,13 @@ public struct FetchAll: Sendable { Element: QueryRepresentable, Element == S.QueryValue.QueryOutput { - let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch( + FetchAllStatementValueRequest(statement: statement), + database: database + ) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Replaces the wrapped value with data from the given query. @@ -237,29 +219,10 @@ public struct FetchAll: Sendable { ) return FetchSubscription(sharedReader: sharedReader) } - - #if !canImport(SwiftUI) - @_transparent - #endif - private func setFetchKeyID( - for request: some FetchKeyRequest, - database: (any DatabaseReader)?, - scheduler: (any ValueObservationScheduler & Hashable)? - ) { - #if canImport(SwiftUI) - box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id - #endif - } } extension FetchAll { - @available( - *, - deprecated, - message: """ - '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. - """ - ) + @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") public init( wrappedValue: [Element] = [], database: (any DatabaseReader)? = nil, @@ -331,12 +294,14 @@ extension FetchAll { Element == V.QueryOutput, V.QueryOutput: Sendable { - let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchAllStatementValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -358,12 +323,14 @@ extension FetchAll { Element: QueryRepresentable, Element == S.QueryValue.QueryOutput { - let request = FetchAllStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchAllStatementValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Replaces the wrapped value with data from the given query. @@ -436,20 +403,10 @@ extension FetchAll: Equatable where Element: Equatable { #if canImport(SwiftUI) extension FetchAll: DynamicProperty { public func update() { - let persisted = state.wrappedValue - if persisted !== box { - persisted.update(from: box) - } - persisted.subscribe(generation: generation) + sharedReader.update() } - @available( - *, - deprecated, - message: """ - '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. - """ - ) + @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") public init( wrappedValue: [Element] = [], database: (any DatabaseReader)? = nil, diff --git a/Sources/SQLiteData/FetchOne.swift b/Sources/SQLiteData/FetchOne.swift index 39f3ba88..4e6b799d 100644 --- a/Sources/SQLiteData/FetchOne.swift +++ b/Sources/SQLiteData/FetchOne.swift @@ -21,32 +21,11 @@ public import StructuredQueriesCore @dynamicMemberLookup @propertyWrapper public struct FetchOne: Sendable { - #if canImport(SwiftUI) - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) - /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader { - @storageRestrictions(initializes: box, state) - init(initialValue) { - let box = FetchBox(sharedReader: initialValue) - self.box = box - state = SwiftUI.State(wrappedValue: box) - } - get { state.wrappedValue.sharedReader } - nonmutating set { state.wrappedValue.sharedReader = newValue } - } - - private let box: FetchBox - private let state: SwiftUI.State> - private let generation = SwiftUI.State(wrappedValue: 0) - #else - /// The underlying shared reader powering the property wrapper. - /// - /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) - /// package, a general solution to observing and persisting changes to external data sources. - public private(set) var sharedReader: SharedReader - #endif + /// The underlying shared reader powering the property wrapper. + /// + /// Shared readers come from the [Sharing](https://github.com/pointfreeco/swift-sharing) package, + /// a general solution to observing and persisting changes to external data sources. + public var sharedReader: SharedReader /// A value associated with the underlying query. public var wrappedValue: Value { @@ -127,12 +106,10 @@ public struct FetchOne: Sendable { Value: StructuredQueriesCore.Table & QueryRepresentable, Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) - let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementValueRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query that fetches the first row from a table. @@ -151,12 +128,10 @@ public struct FetchOne: Sendable { Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) - let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementOptionalProtocolRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -195,12 +170,10 @@ public struct FetchOne: Sendable { where Value == V.QueryOutput { - let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementValueRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -218,12 +191,10 @@ public struct FetchOne: Sendable { where Value == V.QueryOutput? { - let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementOptionalValueRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with the wrapped value. @@ -242,12 +213,10 @@ public struct FetchOne: Sendable { Value: QueryRepresentable, Value == S.QueryValue.QueryOutput { - let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementValueRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with an optional value. @@ -269,12 +238,10 @@ public struct FetchOne: Sendable { S.Joins == () { let statement = statement.selectStar().asSelect().limit(1) - let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementOptionalValueRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with an optional value. @@ -295,12 +262,13 @@ public struct FetchOne: Sendable { S.QueryValue: StructuredQueriesCore._OptionalProtocol, Value == S.QueryValue.QueryOutput { - let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch( + FetchOneStatementOptionalProtocolRequest(statement: statement), + database: database + ) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Initializes this property with a query associated with an optional value. @@ -320,12 +288,10 @@ public struct FetchOne: Sendable { Value: StructuredQueriesCore._OptionalProtocol, Value.QueryOutput == Value { - let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database) + .fetch(FetchOneStatementOptionalProtocolRequest(statement: statement), database: database) ) - setFetchKeyID(for: request, database: database, scheduler: nil) } /// Replaces the wrapped value with data from the given query. @@ -462,29 +428,10 @@ public struct FetchOne: Sendable { ) return FetchSubscription(sharedReader: sharedReader) } - - #if !canImport(SwiftUI) - @_transparent - #endif - private func setFetchKeyID( - for request: some FetchKeyRequest, - database: (any DatabaseReader)?, - scheduler: (any ValueObservationScheduler & Hashable)? - ) { - #if canImport(SwiftUI) - box.fetchKeyID = FetchKey(request: request, database: database, scheduler: scheduler).id - #endif - } } extension FetchOne { - @available( - *, - deprecated, - message: """ - '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. - """ - ) + @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") public init( wrappedValue: sending Value, database: (any DatabaseReader)? = nil, @@ -497,13 +444,7 @@ extension FetchOne { sharedReader = SharedReader(value: wrappedValue) } - @available( - *, - deprecated, - message: """ - '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. - """ - ) + @available(*, deprecated, message: "Remove unused parameters: 'database', 'scheduler'.") public init( wrappedValue: sending Value = Value._none, database: (any DatabaseReader)? = nil, @@ -534,12 +475,14 @@ extension FetchOne { Value: StructuredQueriesCore.Table & QueryRepresentable, Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) - let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query that fetches the first row from a table. @@ -561,12 +504,14 @@ extension FetchOne { Value.QueryOutput == Value { let statement = Value.all.selectStar().asSelect().limit(1) - let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementOptionalProtocolRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -611,12 +556,14 @@ extension FetchOne { where Value == V.QueryOutput { - let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -637,12 +584,14 @@ extension FetchOne { where Value == V.QueryOutput? { - let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementOptionalValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with the wrapped value. @@ -664,12 +613,14 @@ extension FetchOne { Value: QueryRepresentable, Value == S.QueryValue.QueryOutput { - let request = FetchOneStatementValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with an optional value. @@ -694,12 +645,14 @@ extension FetchOne { S.Joins == () { let statement = statement.selectStar().asSelect().limit(1) - let request = FetchOneStatementOptionalValueRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementOptionalValueRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with an optional value. @@ -723,12 +676,14 @@ extension FetchOne { S.QueryValue: StructuredQueriesCore._OptionalProtocol, Value == S.QueryValue.QueryOutput { - let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementOptionalProtocolRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Initializes this property with a query associated with an optional value. @@ -751,12 +706,14 @@ extension FetchOne { Value: StructuredQueriesCore._OptionalProtocol, Value.QueryOutput == Value { - let request = FetchOneStatementOptionalProtocolRequest(statement: statement) sharedReader = SharedReader( wrappedValue: wrappedValue, - .fetch(request, database: database, scheduler: scheduler) + .fetch( + FetchOneStatementOptionalProtocolRequest(statement: statement), + database: database, + scheduler: scheduler + ) ) - setFetchKeyID(for: request, database: database, scheduler: scheduler) } /// Replaces the wrapped value with data from the given query. @@ -948,20 +905,10 @@ extension FetchOne: Equatable where Value: Equatable { #if canImport(SwiftUI) extension FetchOne: DynamicProperty { public func update() { - let persisted = state.wrappedValue - if persisted !== box { - persisted.update(from: box) - } - persisted.subscribe(generation: generation) + sharedReader.update() } - @available( - *, - deprecated, - message: """ - '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. - """ - ) + @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") public init( wrappedValue: sending Value, database: (any DatabaseReader)? = nil, @@ -974,13 +921,7 @@ extension FetchOne: Equatable where Value: Equatable { sharedReader = SharedReader(value: wrappedValue) } - @available( - *, - deprecated, - message: """ - '@Selection' type requires a query to be fetched; provide one or remove unused parameters: 'database', 'scheduler'. - """ - ) + @available(*, deprecated, message: "Remove unused parameters: 'database', 'animation'.") public init( wrappedValue: sending Value = Value._none, database: (any DatabaseReader)? = nil, diff --git a/Sources/SQLiteData/FetchSubscription.swift b/Sources/SQLiteData/FetchSubscription.swift index 00644b50..de794216 100644 --- a/Sources/SQLiteData/FetchSubscription.swift +++ b/Sources/SQLiteData/FetchSubscription.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Perception import Sharing @@ -14,7 +15,7 @@ import Sharing /// } /// ``` public struct FetchSubscription: Sendable { - let cancellable = LockIsolated.Continuation?>(nil) + let cancellable = LockIsolated?>(nil) let onCancel: @Sendable () -> Void init(sharedReader: SharedReader) { @@ -27,17 +28,20 @@ public struct FetchSubscription: Sendable { /// the observation of the associated ``FetchAll``, ``FetchOne``, or ``Fetch``. public var task: Void { get async throws { - let never = AsyncStream { continuation in - cancellable.withLock { $0 = continuation } + let task = Task { + try await withTaskCancellationHandler { + try await Task.never() + } onCancel: { + onCancel() + } } - for await _ in never {} - onCancel() - throw CancellationError() + cancellable.withValue { $0 = task } + try await task.cancellableValue } } /// Cancels the database observation of the associated ``FetchAll``, ``FetchOne``, or ``Fetch``. public func cancel() { - cancellable.withLock { $0?.finish() } + cancellable.value?.cancel() } } diff --git a/Sources/SQLiteData/Internal/FetchBox.swift b/Sources/SQLiteData/Internal/FetchBox.swift deleted file mode 100644 index 7368147d..00000000 --- a/Sources/SQLiteData/Internal/FetchBox.swift +++ /dev/null @@ -1,51 +0,0 @@ -#if canImport(SwiftUI) - import Combine - import Foundation - import Sharing - import SwiftUI - - final class FetchBox: @unchecked Sendable { - private let storage: LockIsolated - - init(sharedReader: SharedReader) { - storage = LockIsolated(Storage(sharedReader: sharedReader)) - } - - var sharedReader: SharedReader { - get { storage.withLock { $0.sharedReader } } - set { storage.withLock { $0.sharedReader = newValue } } - } - - var fetchKeyID: FetchKeyID? { - get { storage.withLock(\.fetchKeyID) } - set { storage.withLock { $0.fetchKeyID = newValue } } - } - - func update(from other: FetchBox) { - guard - let otherFetchKeyID = other.storage.withLock(\.fetchKeyID), - otherFetchKeyID != fetchKeyID - else { return } - storage.withLock { - $0.sharedReader = other.sharedReader - $0.fetchKeyID = other.fetchKeyID - } - } - - func subscribe(generation: SwiftUI.State) { - guard #unavailable(iOS 17, macOS 14, tvOS 17, watchOS 10) else { return } - _ = generation.wrappedValue - storage.withLock { - $0.swiftUICancellable = sharedReader.publisher - .dropFirst() - .sink { _ in generation.wrappedValue &+= 1 } - } - } - - private struct Storage { - var sharedReader: SharedReader - var fetchKeyID: FetchKeyID? - var swiftUICancellable: AnyCancellable? - } - } -#endif diff --git a/Sources/SQLiteData/Internal/FetchKey.swift b/Sources/SQLiteData/Internal/FetchKey.swift index 1ed414cd..8bab73bf 100644 --- a/Sources/SQLiteData/Internal/FetchKey.swift +++ b/Sources/SQLiteData/Internal/FetchKey.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Dependencies import Dispatch import Foundation diff --git a/Sources/SQLiteData/Internal/LockIsolated.swift b/Sources/SQLiteData/Internal/LockIsolated.swift deleted file mode 100644 index ed411ac4..00000000 --- a/Sources/SQLiteData/Internal/LockIsolated.swift +++ /dev/null @@ -1,16 +0,0 @@ -import class Foundation.NSLock - -package final class LockIsolated: @unchecked Sendable { - private var _value: Value - private let lock = NSLock() - package init(_ value: sending Value) { - self._value = value - } - package func withLock( - _ operation: (inout sending Value) throws(F) -> sending R - ) throws(F) -> sending R { - lock.lock() - defer { lock.unlock() } - return try operation(&_value) - } -} diff --git a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift index b93173cc..dd8f0ef9 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AccountLifecycleTests.swift @@ -1,4 +1,5 @@ #if canImport(CloudKit) + import ConcurrencyExtrasTestSupport import CloudKit import CustomDump import Foundation diff --git a/Tests/SQLiteDataTests/CloudKitTests/AssetsTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AssetsTests.swift index 6951da7b..467e85f3 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AssetsTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AssetsTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras import CustomDump import InlineSnapshotTesting import OrderedCollections @@ -58,7 +59,7 @@ """ } - inMemoryDataManager.storage.withLock { storage in + inMemoryDataManager.storage.withValue { storage in let url = URL( string: "file:///tmp/6105d6cc76af400325e94d588ce511be5bfdbb73b437dc51eca43917d7a43e3d" )! @@ -114,7 +115,7 @@ """ } - inMemoryDataManager.storage.withLock { storage in + inMemoryDataManager.storage.withValue { storage in let url = URL( string: "file:///tmp/97e67a5645969953f1a4cfe2ea75649864ff99789189cdd3f6db03e59f8a8ebf" )! diff --git a/Tests/SQLiteDataTests/CloudKitTests/AtomicTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AtomicTests.swift index 1dc55b3a..4b17da1d 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AtomicTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AtomicTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras import CustomDump import InlineSnapshotTesting import OrderedCollections diff --git a/Tests/SQLiteDataTests/CloudKitTests/AttachedMetadatabaseTests.swift b/Tests/SQLiteDataTests/CloudKitTests/AttachedMetadatabaseTests.swift index df8a98fa..dbec8bb2 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/AttachedMetadatabaseTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/AttachedMetadatabaseTests.swift @@ -1,5 +1,7 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras + import ConcurrencyExtrasTestSupport import CustomDump import InlineSnapshotTesting import OrderedCollections diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 75cb5c6f..fa989369 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras import CustomDump import InlineSnapshotTesting import OrderedCollections diff --git a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift index d20b52de..071688d1 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift @@ -672,9 +672,9 @@ """ } assertInlineSnapshot( - of: syncEngine.private.database.state.withLock { - $0.storage[syncEngine.defaultZone.zoneID]?.records[Reminder.recordID(for: 1)] - }, + of: syncEngine.private.database.state.storage[syncEngine.defaultZone.zoneID]?.records[ + Reminder.recordID(for: 1) + ], as: .customDump ) { """ @@ -767,9 +767,9 @@ """ } assertInlineSnapshot( - of: syncEngine.private.database.state.withLock { - $0.storage[syncEngine.defaultZone.zoneID]?.records[Reminder.recordID(for: 1)] - }, + of: syncEngine.private.database.state.storage[syncEngine.defaultZone.zoneID]?.records[ + Reminder.recordID(for: 1) + ], as: .customDump ) { """ diff --git a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift index ceb1a3f2..b9840148 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MergeConflictTests.swift @@ -1,6 +1,6 @@ #if canImport(CloudKit) - import ConcurrencyExtrasTestSupport import CloudKit + import ConcurrencyExtrasTestSupport import CustomDump import Foundation import InlineSnapshotTesting diff --git a/Tests/SQLiteDataTests/CloudKitTests/MetadataTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MetadataTests.swift index 945b33da..bd81326d 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MetadataTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MetadataTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtrasTestSupport import CustomDump import SQLiteDataTestSupport import Foundation diff --git a/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift b/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift index fca888db..d019ae9e 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/MockCloudDatabaseTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras import CustomDump import InlineSnapshotTesting import OrderedCollections @@ -320,7 +321,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func accountTemporarilyAvailable() async throws { - container._accountStatus.withLock { $0 = .temporarilyUnavailable } + container._accountStatus.withValue { $0 = .temporarilyUnavailable } var error = #expect(throws: CKError.self) { _ = try self.syncEngine.private.database.modifyRecordZones() } @@ -343,7 +344,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func noAccount() async throws { - container._accountStatus.withLock { $0 = .noAccount } + container._accountStatus.withValue { $0 = .noAccount } var error = #expect(throws: CKError.self) { _ = try self.syncEngine.private.database.modifyRecordZones() } @@ -366,7 +367,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func accountNotDetermined() async throws { - container._accountStatus.withLock { $0 = .couldNotDetermine } + container._accountStatus.withValue { $0 = .couldNotDetermine } var error = #expect(throws: CKError.self) { _ = try self.syncEngine.private.database.modifyRecordZones() } @@ -389,7 +390,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func restrictedAccount() async throws { - container._accountStatus.withLock { $0 = .restricted } + container._accountStatus.withValue { $0 = .restricted } var error = #expect(throws: CKError.self) { _ = try self.syncEngine.private.database.modifyRecordZones() } diff --git a/Tests/SQLiteDataTests/CloudKitTests/NewTableSyncTests.swift b/Tests/SQLiteDataTests/CloudKitTests/NewTableSyncTests.swift index 0bdb99df..568184a5 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/NewTableSyncTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/NewTableSyncTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtrasTestSupport import CustomDump import SQLiteDataTestSupport import Foundation diff --git a/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift b/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift index d8fbbed2..8a3c2066 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/NextRecordZoneChangeBatchTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtrasTestSupport import CustomDump import Foundation import InlineSnapshotTesting diff --git a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift index a9b494c6..2891b3e2 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras import CustomDump import InlineSnapshotTesting import SQLiteData diff --git a/Tests/SQLiteDataTests/CloudKitTests/ReferenceViolationTests.swift b/Tests/SQLiteDataTests/CloudKitTests/ReferenceViolationTests.swift index c3871cbe..eaabf060 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/ReferenceViolationTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/ReferenceViolationTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtras import CustomDump import InlineSnapshotTesting import SQLiteData diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index e93e5c35..98601572 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -791,7 +791,7 @@ ) .notify() - inMemoryDataManager.storage.withLock { $0.removeAll() } + inMemoryDataManager.storage.withValue { $0.removeAll() } try await userDatabase.userWrite { db in try #sql( @@ -848,7 +848,7 @@ .notify() syncEngine.stop() - inMemoryDataManager.storage.withLock { $0.removeAll() } + inMemoryDataManager.storage.withValue { $0.removeAll() } try await userDatabase.userWrite { db in try #sql( diff --git a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift index ba97ea2b..395be01e 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineDelegateTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtrasTestSupport import CustomDump import DependenciesTestSupport import Foundation @@ -228,10 +229,10 @@ _ syncEngine: SQLiteData.SyncEngine, accountChanged changeType: CKSyncEngine.Event.AccountChange.ChangeType ) async { - wasCalled.withLock { $0 = true } + wasCalled.withValue { $0 = true } } deinit { - guard wasCalled.withLock(\.self) + guard wasCalled.withValue(\.self) else { Issue.record("Delegate method 'syncEngine(_:accountChanged:)' was not called.") return diff --git a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift index e651ba27..229d6b02 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineLifecycleTests.swift @@ -1,5 +1,6 @@ #if canImport(CloudKit) import CloudKit + import ConcurrencyExtrasTestSupport import DependenciesTestSupport import InlineSnapshotTesting import SQLiteDataTestSupport diff --git a/Tests/SQLiteDataTests/FetchBoxTests.swift b/Tests/SQLiteDataTests/FetchBoxTests.swift deleted file mode 100644 index c4016cfd..00000000 --- a/Tests/SQLiteDataTests/FetchBoxTests.swift +++ /dev/null @@ -1,78 +0,0 @@ -#if canImport(SwiftUI) - import GRDB - import Sharing - import Testing - - @testable import SQLiteData - - @Suite struct FetchBoxTests { - let database: any DatabaseReader - - init() throws { - database = try DatabaseQueue() - } - - @Test func keyedReinitializationWithNewQueryIsAdopted() { - let persisted = FetchBox(sharedReader: SharedReader(value: 1)) - persisted.fetchKeyID = fetchKeyID(TestRequest(id: 1)) - let fresh = FetchBox(sharedReader: SharedReader(value: 2)) - fresh.fetchKeyID = fetchKeyID(TestRequest(id: 2)) - persisted.update(from: fresh) - #expect(persisted.sharedReader.wrappedValue == 2) - #expect(persisted.fetchKeyID == fresh.fetchKeyID) - } - - @Test func keyedReinitializationWithSameQueryIsIgnored() { - let persisted = FetchBox(sharedReader: SharedReader(value: 1)) - persisted.fetchKeyID = fetchKeyID(TestRequest(id: 1)) - let fresh = FetchBox(sharedReader: SharedReader(value: 2)) - fresh.fetchKeyID = fetchKeyID(TestRequest(id: 1)) - persisted.update(from: fresh) - #expect(persisted.sharedReader.wrappedValue == 1) - } - - @Test func keyedToKeylessReinitializationIsIgnored() { - let persisted = FetchBox(sharedReader: SharedReader(value: 1)) - persisted.fetchKeyID = fetchKeyID(TestRequest(id: 1)) - let fresh = FetchBox(sharedReader: SharedReader(value: 2)) - persisted.update(from: fresh) - #expect(persisted.sharedReader.wrappedValue == 1) - #expect(persisted.fetchKeyID != nil) - } - - @Test func keylessReinitializationIsIgnored() { - let persisted = FetchBox(sharedReader: SharedReader(value: 1)) - let fresh = FetchBox(sharedReader: SharedReader(value: 2)) - persisted.update(from: fresh) - #expect(persisted.sharedReader.wrappedValue == 1) - } - - @Test func keylessToKeyedReinitializationIsAdopted() { - let persisted = FetchBox(sharedReader: SharedReader(value: 1)) - let fresh = FetchBox(sharedReader: SharedReader(value: 2)) - fresh.fetchKeyID = fetchKeyID(TestRequest(id: 2)) - persisted.update(from: fresh) - #expect(persisted.sharedReader.wrappedValue == 2) - #expect(persisted.fetchKeyID == fresh.fetchKeyID) - } - - @Test func keylessReinitializationAfterLocalLoadIsIgnored() { - let persisted = FetchBox(sharedReader: SharedReader(value: [Int]())) - persisted.sharedReader = SharedReader(value: [1, 2, 3]) - let fresh = FetchBox(sharedReader: SharedReader(value: [Int]())) - persisted.update(from: fresh) - #expect(persisted.sharedReader.wrappedValue == [1, 2, 3]) - } - - private func fetchKeyID(_ request: some FetchKeyRequest) -> FetchKeyID { - FetchKey(request: request, database: database, scheduler: nil).id - } - } - - private struct TestRequest: FetchKeyRequest, Hashable { - let id: Int - func fetch(_ db: Database) throws -> Int { - id - } - } -#endif diff --git a/Tests/SQLiteDataTests/FetchSubscriptionTests.swift b/Tests/SQLiteDataTests/FetchSubscriptionTests.swift index 25c3702d..155dcdc2 100644 --- a/Tests/SQLiteDataTests/FetchSubscriptionTests.swift +++ b/Tests/SQLiteDataTests/FetchSubscriptionTests.swift @@ -31,7 +31,7 @@ import Testing @Test func completeWhenTaskExplicitlyCancelled() async throws { @FetchAll var records: [Record] #expect(records.count == 0) - nonisolated(unsafe) var didComplete = false + let didComplete = LockIsolated(false) try await database.write { db in try Record.insert { Record.Draft() }.execute(db) @@ -43,14 +43,14 @@ import Testing let task = Task { try? await subscription.task - didComplete = true + didComplete.withValue { $0 = true } } try await Task.sleep(for: .seconds(1)) subscription.cancel() await task.value - #expect(didComplete) + #expect(didComplete.value) } @Test func cancellingOneFetchDoesNotCancelAnother() async throws { diff --git a/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift b/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift index ea6163f4..f55e5a64 100644 --- a/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift +++ b/Tests/SQLiteDataTests/Internal/BaseCloudKitTests.swift @@ -1,6 +1,7 @@ #if canImport(CloudKit) import Clocks import CloudKit + import ConcurrencyExtrasTestSupport import DependenciesTestSupport import OrderedCollections import SQLiteData @@ -99,7 +100,7 @@ import TestLocals @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) func signOut() async { - container._accountStatus.withLock { $0 = .noAccount } + container._accountStatus.withValue { $0 = .noAccount } await syncEngine.handleEvent( .accountChange(changeType: .signOut(previousUser: previousUserRecordID)), syncEngine: syncEngine.private @@ -112,12 +113,12 @@ import TestLocals @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) func softSignOut() async { - container._accountStatus.withLock { $0 = .temporarilyUnavailable } + container._accountStatus.withValue { $0 = .temporarilyUnavailable } } @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) func signIn() async { - container._accountStatus.withLock { $0 = .available } + container._accountStatus.withValue { $0 = .available } // NB: Emulates what CKSyncEngine does when signing in syncEngine.private.state.removePendingChanges() syncEngine.shared.state.removePendingChanges() @@ -157,10 +158,10 @@ import TestLocals @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension SyncEngine { var `private`: MockSyncEngine { - syncEngines.withLock(\.private) as! MockSyncEngine + syncEngines.private as! MockSyncEngine } var shared: MockSyncEngine { - syncEngines.withLock(\.shared) as! MockSyncEngine + syncEngines.shared as! MockSyncEngine } static nonisolated let defaultTestZone = CKRecordZone( zoneName: "zone" diff --git a/Tests/SQLiteDataTests/Internal/CloudKit+CustomDump.swift b/Tests/SQLiteDataTests/Internal/CloudKit+CustomDump.swift index e4aab1e4..16d1903b 100644 --- a/Tests/SQLiteDataTests/Internal/CloudKit+CustomDump.swift +++ b/Tests/SQLiteDataTests/Internal/CloudKit+CustomDump.swift @@ -175,13 +175,13 @@ children: [ ( "pendingRecordZoneChanges", - _pendingRecordZoneChanges.withLock(\.self) + _pendingRecordZoneChanges.withValue(\.self) .sorted(by: comparePendingRecordZoneChange) as Any ), ( "pendingDatabaseChanges", - _pendingDatabaseChanges.withLock(\.self) + _pendingDatabaseChanges.withValue(\.self) .sorted(by: comparePendingDatabaseChange) as Any ), ], @@ -249,12 +249,11 @@ children: [ "databaseScope": databaseScope, "storage": state - .withLock { - $0.storage - .flatMap { _, value in value.records.values } - .sorted { - ($0.recordType, $0.recordID.recordName) < ($1.recordType, $1.recordID.recordName) - } + .value + .storage + .flatMap { _, value in value.records.values } + .sorted { + ($0.recordType, $0.recordID.recordName) < ($1.recordType, $1.recordID.recordName) }, ], displayStyle: .struct diff --git a/Tests/SQLiteDataTests/Internal/CloudKitTestHelpers.swift b/Tests/SQLiteDataTests/Internal/CloudKitTestHelpers.swift index 580f512f..4716be7b 100644 --- a/Tests/SQLiteDataTests/Internal/CloudKitTestHelpers.swift +++ b/Tests/SQLiteDataTests/Internal/CloudKitTestHelpers.swift @@ -1,4 +1,5 @@ import CloudKit +import ConcurrencyExtras import CustomDump import OrderedCollections import SQLiteData @@ -74,7 +75,7 @@ extension SyncEngine { > { let syncEngine = syncEngine(for: scope) let recordsToDeleteByID = Dictionary( - grouping: syncEngine.database.state.withLock { state in + grouping: syncEngine.database.state.withValue { state in recordIDsToDelete.compactMap { recordID in state.storage[recordID.zoneID]?.records[recordID] } @@ -117,7 +118,7 @@ extension MockSyncEngine { line: UInt = #line, column: UInt = #column ) { - _fetchChangesScopes.withLock { + _fetchChangesScopes.withValue { expectNoDifference( scopes, $0, @@ -137,7 +138,7 @@ extension MockSyncEngine { line: UInt = #line, column: UInt = #column ) { - _acceptedShareMetadata.withLock { + _acceptedShareMetadata.withValue { expectNoDifference( sharedMetadata, $0, @@ -160,7 +161,7 @@ extension MockSyncEngineState { line: UInt = #line, column: UInt = #column ) { - _pendingRecordZoneChanges.withLock { + _pendingRecordZoneChanges.withValue { expectNoDifference( Set(changes), Set($0), @@ -180,7 +181,7 @@ extension MockSyncEngineState { line: UInt = #line, column: UInt = #column ) { - _pendingDatabaseChanges.withLock { + _pendingDatabaseChanges.withValue { expectNoDifference( Set(changes), Set($0),