From 8d6a692f6906cff779b50ee80a2cffaa8251cc9f Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 13 Nov 2023 09:36:52 -0300 Subject: [PATCH] refactor: use swift-concurrency-extras --- Package.swift | 27 ++++++++- Sources/GoTrue/Internal/Dependencies.swift | 2 +- Sources/GoTrue/Internal/EventEmitter.swift | 2 +- Sources/PostgREST/PostgrestBuilder.swift | 5 +- Sources/Realtime/RealtimeChannel.swift | 3 +- Sources/Realtime/RealtimeClient.swift | 17 +++--- Sources/Supabase/SupabaseClient.swift | 1 + Sources/_Helpers/ActorIsolated.swift | 28 --------- Sources/_Helpers/AsyncStream.swift | 21 ------- Sources/_Helpers/LockIsolated.swift | 57 ------------------- Sources/_Helpers/Task.swift | 17 ------ .../FunctionsTests/FunctionsClientTests.swift | 6 +- Tests/GoTrueTests/GoTrueClientTests.swift | 6 +- Tests/GoTrueTests/Mocks/Mocks.swift | 3 +- Tests/GoTrueTests/SessionManagerTests.swift | 8 ++- .../PostgRESTTests/BuildURLRequestTests.swift | 5 +- .../xcshareddata/swiftpm/Package.resolved | 9 +++ 17 files changed, 67 insertions(+), 150 deletions(-) delete mode 100644 Sources/_Helpers/ActorIsolated.swift delete mode 100644 Sources/_Helpers/AsyncStream.swift delete mode 100644 Sources/_Helpers/LockIsolated.swift delete mode 100644 Sources/_Helpers/Task.swift diff --git a/Package.swift b/Package.swift index c74e352f..8b077ce8 100644 --- a/Package.swift +++ b/Package.swift @@ -28,15 +28,23 @@ let package = Package( .package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.2"), .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.8.1"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), + .package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.0.0"), ], targets: [ .target(name: "_Helpers"), .target(name: "Functions", dependencies: ["_Helpers"]), - .testTarget(name: "FunctionsTests", dependencies: ["Functions"]), + .testTarget( + name: "FunctionsTests", + dependencies: [ + "Functions", + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), + ] + ), .target( name: "GoTrue", dependencies: [ "_Helpers", + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), .product(name: "KeychainAccess", package: "KeychainAccess"), ] ), @@ -49,7 +57,13 @@ let package = Package( ], resources: [.process("Resources")] ), - .target(name: "PostgREST", dependencies: ["_Helpers"]), + .target( + name: "PostgREST", + dependencies: [ + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), + "_Helpers", + ] + ), .testTarget( name: "PostgRESTTests", dependencies: [ @@ -60,13 +74,20 @@ let package = Package( exclude: ["__Snapshots__"] ), .testTarget(name: "PostgRESTIntegrationTests", dependencies: ["PostgREST"]), - .target(name: "Realtime", dependencies: ["_Helpers"]), + .target( + name: "Realtime", + dependencies: [ + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), + "_Helpers", + ] + ), .testTarget(name: "RealtimeTests", dependencies: ["Realtime"]), .target(name: "Storage", dependencies: ["_Helpers"]), .testTarget(name: "StorageTests", dependencies: ["Storage"]), .target( name: "Supabase", dependencies: [ + .product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"), "GoTrue", "Storage", "Realtime", diff --git a/Sources/GoTrue/Internal/Dependencies.swift b/Sources/GoTrue/Internal/Dependencies.swift index 2c3907ca..8ee31963 100644 --- a/Sources/GoTrue/Internal/Dependencies.swift +++ b/Sources/GoTrue/Internal/Dependencies.swift @@ -1,5 +1,5 @@ +import ConcurrencyExtras import Foundation -@_spi(Internal) import _Helpers struct Dependencies: Sendable { static let current = LockIsolated(Dependencies?.none) diff --git a/Sources/GoTrue/Internal/EventEmitter.swift b/Sources/GoTrue/Internal/EventEmitter.swift index daec152c..bea9d946 100644 --- a/Sources/GoTrue/Internal/EventEmitter.swift +++ b/Sources/GoTrue/Internal/EventEmitter.swift @@ -1,5 +1,5 @@ +import ConcurrencyExtras import Foundation -@_spi(Internal) import _Helpers struct EventEmitter: Sendable { var attachListener: @Sendable () -> ( diff --git a/Sources/PostgREST/PostgrestBuilder.swift b/Sources/PostgREST/PostgrestBuilder.swift index 2c2a56d1..98c5b471 100644 --- a/Sources/PostgREST/PostgrestBuilder.swift +++ b/Sources/PostgREST/PostgrestBuilder.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Foundation @_spi(Internal) import _Helpers @@ -18,7 +19,7 @@ public class PostgrestBuilder: @unchecked Sendable { var fetchOptions: FetchOptions } - let mutableState: ActorIsolated + let mutableState: LockIsolated init( configuration: PostgrestClient.Configuration, @@ -27,7 +28,7 @@ public class PostgrestBuilder: @unchecked Sendable { self.configuration = configuration http = HTTPClient(fetchHandler: configuration.fetch) - mutableState = ActorIsolated( + mutableState = LockIsolated( MutableState( request: request, fetchOptions: FetchOptions() diff --git a/Sources/Realtime/RealtimeChannel.swift b/Sources/Realtime/RealtimeChannel.swift index 74007473..6d2eceaa 100644 --- a/Sources/Realtime/RealtimeChannel.swift +++ b/Sources/Realtime/RealtimeChannel.swift @@ -21,6 +21,7 @@ import Foundation import Swift @_spi(Internal) import _Helpers +import ConcurrencyExtras /// Container class of bindings to the channel struct Binding { @@ -439,7 +440,7 @@ public class RealtimeChannel { } } - self.bindings.withValue { + self.bindings.withValue { [newPostgresBindings] in $0["postgres_changes"] = newPostgresBindings } callback?(.subscribed, nil) diff --git a/Sources/Realtime/RealtimeClient.swift b/Sources/Realtime/RealtimeClient.swift index 6031a933..31ab4608 100644 --- a/Sources/Realtime/RealtimeClient.swift +++ b/Sources/Realtime/RealtimeClient.swift @@ -20,6 +20,7 @@ import Foundation @_spi(Internal) import _Helpers +import ConcurrencyExtras public enum SocketError: Error { case abnormalClosureError @@ -396,7 +397,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated() delegated.manuallyDelegate(with: callback) - return stateChangeCallbacks.open.withValue { + return stateChangeCallbacks.open.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } @@ -439,7 +440,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated() delegated.delegate(to: owner, with: callback) - return stateChangeCallbacks.open.withValue { + return stateChangeCallbacks.open.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } @@ -474,7 +475,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated<(Int, String?), Void>() delegated.manuallyDelegate(with: callback) - return stateChangeCallbacks.close.withValue { + return stateChangeCallbacks.close.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } @@ -517,7 +518,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated<(Int, String?), Void>() delegated.delegate(to: owner, with: callback) - return stateChangeCallbacks.close.withValue { + return stateChangeCallbacks.close.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } @@ -537,7 +538,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated<(Error, URLResponse?), Void>() delegated.manuallyDelegate(with: callback) - return stateChangeCallbacks.error.withValue { + return stateChangeCallbacks.error.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } @@ -561,7 +562,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated<(Error, URLResponse?), Void>() delegated.delegate(to: owner, with: callback) - return stateChangeCallbacks.error.withValue { + return stateChangeCallbacks.error.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } @@ -582,7 +583,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated() delegated.manuallyDelegate(with: callback) - return stateChangeCallbacks.message.withValue { + return stateChangeCallbacks.message.withValue { [delegated] in append(callback: delegated, to: &$0) } } @@ -606,7 +607,7 @@ public class RealtimeClient: PhoenixTransportDelegate { var delegated = Delegated() delegated.delegate(to: owner, with: callback) - return stateChangeCallbacks.message.withValue { + return stateChangeCallbacks.message.withValue { [delegated] in self.append(callback: delegated, to: &$0) } } diff --git a/Sources/Supabase/SupabaseClient.swift b/Sources/Supabase/SupabaseClient.swift index 3a12eede..03edbe34 100644 --- a/Sources/Supabase/SupabaseClient.swift +++ b/Sources/Supabase/SupabaseClient.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Foundation @_spi(Internal) import _Helpers @_exported import Functions diff --git a/Sources/_Helpers/ActorIsolated.swift b/Sources/_Helpers/ActorIsolated.swift deleted file mode 100644 index aaf24147..00000000 --- a/Sources/_Helpers/ActorIsolated.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// ActorIsolated.swift -// -// -// Created by Guilherme Souza on 07/10/23. -// - -import Foundation - -@_spi(Internal) -public final class ActorIsolated { - public var value: Value - - public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { - self.value = try value() - } - - @discardableResult - public func withValue(_ block: @Sendable (inout Value) throws -> T) rethrows -> T { - var value = value - defer { self.value = value } - return try block(&value) - } - - public func setValue(_ newValue: @autoclosure @Sendable () throws -> Value) rethrows { - value = try newValue() - } -} diff --git a/Sources/_Helpers/AsyncStream.swift b/Sources/_Helpers/AsyncStream.swift deleted file mode 100644 index a4100b4e..00000000 --- a/Sources/_Helpers/AsyncStream.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// AsyncStream.swift -// -// -// Created by Guilherme Souza on 26/10/23. -// - -import Foundation - -extension AsyncStream { - #if compiler(<5.9) - @_spi(Internal) - public static func makeStream( - of elementType: Element.Type = Element.self, - bufferingPolicy limit: Continuation.BufferingPolicy = .unbounded - ) -> (stream: Self, continuation: Continuation) { - var continuation: Continuation! - return (Self(elementType, bufferingPolicy: limit) { continuation = $0 }, continuation) - } - #endif -} diff --git a/Sources/_Helpers/LockIsolated.swift b/Sources/_Helpers/LockIsolated.swift deleted file mode 100644 index c3d1e657..00000000 --- a/Sources/_Helpers/LockIsolated.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -@_spi(Internal) -public final class LockIsolated: @unchecked Sendable { - private var _value: Value - private let lock = NSRecursiveLock() - - public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows { - _value = try value() - } - - public func withValue( - _ operation: (inout Value) throws -> T - ) rethrows -> T { - try lock.sync { - var value = self._value - defer { self._value = value } - return try operation(&value) - } - } - - public func setValue(_ newValue: @autoclosure @Sendable () throws -> Value) rethrows { - try lock.sync { - self._value = try newValue() - } - } -} - -extension LockIsolated where Value: Sendable { - /// The lock-isolated value. - public var value: Value { - lock.sync { - self._value - } - } -} - -extension LockIsolated: Equatable where Value: Equatable { - public static func == (lhs: LockIsolated, rhs: LockIsolated) -> Bool { - lhs.withValue { lhsValue in rhs.withValue { rhsValue in lhsValue == rhsValue } } - } -} - -extension LockIsolated: Hashable where Value: Hashable { - public func hash(into hasher: inout Hasher) { - withValue { hasher.combine($0) } - } -} - -extension NSRecursiveLock { - @inlinable @discardableResult - @_spi(Internal) public func sync(work: () throws -> R) rethrows -> R { - lock() - defer { self.unlock() } - return try work() - } -} diff --git a/Sources/_Helpers/Task.swift b/Sources/_Helpers/Task.swift deleted file mode 100644 index 51c45765..00000000 --- a/Sources/_Helpers/Task.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Task.swift -// -// -// Created by Guilherme Souza on 26/10/23. -// - -import Foundation - -extension Task where Success == Never, Failure == Never { - @_spi(Internal) - public static func megaYield(count: Int = 20) async { - for _ in 0 ..< count { - await Task.detached(priority: .background) { await Task.yield() }.value - } - } -} diff --git a/Tests/FunctionsTests/FunctionsClientTests.swift b/Tests/FunctionsTests/FunctionsClientTests.swift index 7a9a00cd..4c4745fa 100644 --- a/Tests/FunctionsTests/FunctionsClientTests.swift +++ b/Tests/FunctionsTests/FunctionsClientTests.swift @@ -1,5 +1,5 @@ import XCTest - +import ConcurrencyExtras @_spi(Internal) import _Helpers @testable import Functions @@ -14,7 +14,7 @@ final class FunctionsClientTests: XCTestCase { let _request = ActorIsolated(URLRequest?.none) let sut = FunctionsClient(url: self.url, headers: ["apikey": apiKey]) { request in - _request.setValue(request) + await _request.setValue(request) return ( Data(), HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil)! ) @@ -27,7 +27,7 @@ final class FunctionsClientTests: XCTestCase { options: .init(headers: ["X-Custom-Key": "value"], body: body) ) - let request = _request.value + let request = await _request.value XCTAssertEqual(request?.url, url) XCTAssertEqual(request?.httpMethod, "POST") diff --git a/Tests/GoTrueTests/GoTrueClientTests.swift b/Tests/GoTrueTests/GoTrueClientTests.swift index d25f7457..c3fc300f 100644 --- a/Tests/GoTrueTests/GoTrueClientTests.swift +++ b/Tests/GoTrueTests/GoTrueClientTests.swift @@ -7,6 +7,7 @@ import XCTest @_spi(Internal) import _Helpers +import ConcurrencyExtras @testable import GoTrue @@ -28,7 +29,7 @@ final class GoTrueClientTests: XCTestCase { let streamTask = Task { for await (event, _) in authStateStream { - events.withValue { + await events.withValue { $0.append(event) } @@ -38,7 +39,8 @@ final class GoTrueClientTests: XCTestCase { await fulfillment(of: [expectation]) - XCTAssertEqual(events.value, [.initialSession]) + let events = await events.value + XCTAssertEqual(events, [.initialSession]) streamTask.cancel() } diff --git a/Tests/GoTrueTests/Mocks/Mocks.swift b/Tests/GoTrueTests/Mocks/Mocks.swift index 976814e8..ec52fbfc 100644 --- a/Tests/GoTrueTests/Mocks/Mocks.swift +++ b/Tests/GoTrueTests/Mocks/Mocks.swift @@ -72,7 +72,8 @@ func withDependencies( let current = Dependencies.current.value ?? .mock var copy = current mutation(©) - Dependencies.current.withValue { $0 = copy } + let copied = copy + Dependencies.current.withValue { $0 = copied } defer { Dependencies.current.setValue(current) } try await operation() } diff --git a/Tests/GoTrueTests/SessionManagerTests.swift b/Tests/GoTrueTests/SessionManagerTests.swift index 5a6128af..87ecb5a3 100644 --- a/Tests/GoTrueTests/SessionManagerTests.swift +++ b/Tests/GoTrueTests/SessionManagerTests.swift @@ -8,6 +8,7 @@ import XCTest import XCTestDynamicOverlay @_spi(Internal) import _Helpers +import ConcurrencyExtras @testable import GoTrue @@ -51,7 +52,7 @@ final class SessionManagerTests: XCTestCase { let currentSession = Session.expiredSession let validSession = Session.validSession - let storeSessionCallCount = ActorIsolated(0) + let storeSessionCallCount = LockIsolated(0) let refreshSessionCallCount = ActorIsolated(0) let (refreshSessionStream, refreshSessionContinuation) = AsyncStream.makeStream() @@ -66,7 +67,7 @@ final class SessionManagerTests: XCTestCase { } } $0.sessionRefresher.refreshSession = { _ in - refreshSessionCallCount.withValue { $0 += 1 } + await refreshSessionCallCount.withValue { $0 += 1 } return await refreshSessionStream.first { _ in true } ?? .empty } } operation: { @@ -92,7 +93,8 @@ final class SessionManagerTests: XCTestCase { } // Verify that refresher and storage was called only once. - XCTAssertEqual(refreshSessionCallCount.value, 1) + let refreshSessionCallCount = await refreshSessionCallCount.value + XCTAssertEqual(refreshSessionCallCount, 1) XCTAssertEqual(storeSessionCallCount.value, 1) XCTAssertEqual(try result.map { try $0.get() }, (0 ..< 10).map { _ in validSession }) } diff --git a/Tests/PostgRESTTests/BuildURLRequestTests.swift b/Tests/PostgRESTTests/BuildURLRequestTests.swift index 006173d6..0bf6cc0c 100644 --- a/Tests/PostgRESTTests/BuildURLRequestTests.swift +++ b/Tests/PostgRESTTests/BuildURLRequestTests.swift @@ -1,3 +1,4 @@ +import ConcurrencyExtras import Foundation import SnapshotTesting import XCTest @@ -27,7 +28,7 @@ final class BuildURLRequestTests: XCTestCase { schema: nil, headers: ["X-Client-Info": "postgrest-swift/x.y.z"], fetch: { request in - guard let runningTestCase = runningTestCase.value else { + guard let runningTestCase = await runningTestCase.value else { XCTFail("execute called without a runningTestCase set.") return (Data(), URLResponse()) } @@ -108,7 +109,7 @@ final class BuildURLRequestTests: XCTestCase { ] for testCase in testCases { - runningTestCase.withValue { $0 = testCase } + await runningTestCase.withValue { $0 = testCase } let builder = try await testCase.build(client) _ = try? await builder.execute() } diff --git a/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved b/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved index f36aa906..45a1905b 100644 --- a/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/supabase-swift.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -36,6 +36,15 @@ "version" : "1.0.5" } }, + { + "identity" : "swift-concurrency-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-concurrency-extras", + "state" : { + "revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71", + "version" : "1.1.0" + } + }, { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl",