From 1bdba2259e4b41417ac7fe78aee61b373fffc8c3 Mon Sep 17 00:00:00 2001 From: Eric Lewis Date: Tue, 1 Mar 2022 14:38:01 -0500 Subject: [PATCH 1/2] Conform to Sendable Conform internal models to Sendable. There are two classes which are unchecked Sendables and use an NSLock to manage access to the mutable values. --- Sources/Get/APIClient.swift | 12 ++++++------ Sources/Get/Helpers.swift | 35 +++++++++++++++++++++++++++++++---- Sources/Get/Request.swift | 6 ++++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/Sources/Get/APIClient.swift b/Sources/Get/APIClient.swift index 6ad07a4..dcaf7b5 100644 --- a/Sources/Get/APIClient.swift +++ b/Sources/Get/APIClient.swift @@ -2,9 +2,9 @@ // // Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean). -import Foundation +@preconcurrency import Foundation -public protocol APIClientDelegate { +public protocol APIClientDelegate: Sendable { func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws func shouldClientRetry(_ client: APIClient, withError error: Error) async throws -> Bool func client(_ client: APIClient, didReceiveInvalidResponse response: HTTPURLResponse, data: Data) -> Error @@ -17,7 +17,7 @@ public actor APIClient { private let delegate: APIClientDelegate private let loader = DataLoader() - public struct Configuration { + public struct Configuration: Sendable { public var host: String public var port: Int? /// If `true`, uses `http` instead of `https`. @@ -60,7 +60,7 @@ public actor APIClient { } /// Sends the given request and returns a response with a decoded response value. - public func send(_ request: Request) async throws -> Response { + public func send(_ request: Request) async throws -> Response where T: Sendable { try await send(request) { data in if data.isEmpty { return nil @@ -71,11 +71,11 @@ public actor APIClient { } /// Sends the given request and returns a response with a decoded response value. - public func send(_ request: Request) async throws -> Response { + public func send(_ request: Request) async throws -> Response where T: Sendable { try await send(request, decode) } - private func decode(_ data: Data) async throws -> T { + private func decode(_ data: Data) async throws -> T where T: Sendable { if T.self == Data.self { return data as! T } else if T.self == String.self { diff --git a/Sources/Get/Helpers.swift b/Sources/Get/Helpers.swift index aabd19d..b25662a 100644 --- a/Sources/Get/Helpers.swift +++ b/Sources/Get/Helpers.swift @@ -2,7 +2,7 @@ // // Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean). -import Foundation +@preconcurrency import Foundation struct AnyEncodable: Encodable { private let value: Encodable @@ -67,13 +67,40 @@ actor Serializer { } // A simple URLSession wrapper adding async/await APIs compatible with older platforms. -final class DataLoader: NSObject, URLSessionDataDelegate { - private var handlers = [URLSessionTask: TaskHandler]() +final class DataLoader: NSObject, URLSessionDataDelegate, @unchecked Sendable { + private var _handlers = [URLSessionTask: TaskHandler]() + private var handlers: [URLSessionTask: TaskHandler] { + get { safe { _handlers } } + set { safe { _handlers = newValue } } + } + + private let lock = NSRecursiveLock() + private func safe(_ action: () -> Value) -> Value { + defer { lock.unlock() } + lock.lock() + return action() + } + private typealias Completion = (Result<(Data, URLResponse, URLSessionTaskMetrics?), Error>) -> Void /// Loads data with the given request. func data(for request: URLRequest, session: URLSession) async throws -> (Data, URLResponse, URLSessionTaskMetrics?) { - final class Box { var task: URLSessionTask? } + final class Box: @unchecked Sendable { + private let lock = NSRecursiveLock() + private var _task: URLSessionTask? + + var task: URLSessionTask? { + get { safe { _task } } + set { safe { _task = newValue } } + } + + private func safe(_ action: () -> Value) -> Value { + defer { lock.unlock() } + lock.lock() + return action() + } + } + let box = Box() return try await withTaskCancellationHandler(handler: { box.task?.cancel() diff --git a/Sources/Get/Request.swift b/Sources/Get/Request.swift index 3dd739d..31fc52d 100644 --- a/Sources/Get/Request.swift +++ b/Sources/Get/Request.swift @@ -2,9 +2,9 @@ // // Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean). -import Foundation +@preconcurrency import Foundation -public struct Request { +public struct Request: Sendable { public var method: String public var path: String public var query: [(String, String?)]? @@ -99,3 +99,5 @@ public struct Response { Response(value: closure(value), data: data, request: request, response: response, metrics: metrics) } } + +extension Response: Sendable where T: Sendable {} From 4fc99718532e26751199c48c962fc44bb04a2f17 Mon Sep 17 00:00:00 2001 From: Eric Lewis Date: Thu, 24 Mar 2022 09:41:31 -0400 Subject: [PATCH 2/2] support swift < 5.6 --- Sources/Get/APIClient.swift | 4 ++++ Sources/Get/Helpers.swift | 4 ++++ Sources/Get/Request.swift | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/Sources/Get/APIClient.swift b/Sources/Get/APIClient.swift index dcaf7b5..82c63ca 100644 --- a/Sources/Get/APIClient.swift +++ b/Sources/Get/APIClient.swift @@ -2,7 +2,11 @@ // // Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean). +#if compiler(>=5.6) @preconcurrency import Foundation +#else +import Foundation +#endif public protocol APIClientDelegate: Sendable { func client(_ client: APIClient, willSendRequest request: inout URLRequest) async throws diff --git a/Sources/Get/Helpers.swift b/Sources/Get/Helpers.swift index b25662a..554c948 100644 --- a/Sources/Get/Helpers.swift +++ b/Sources/Get/Helpers.swift @@ -2,7 +2,11 @@ // // Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean). +#if compiler(>=5.6) @preconcurrency import Foundation +#else +import Foundation +#endif struct AnyEncodable: Encodable { private let value: Encodable diff --git a/Sources/Get/Request.swift b/Sources/Get/Request.swift index 31fc52d..81a731c 100644 --- a/Sources/Get/Request.swift +++ b/Sources/Get/Request.swift @@ -2,7 +2,11 @@ // // Copyright (c) 2021-2022 Alexander Grebenyuk (github.com/kean). +#if compiler(>=5.6) @preconcurrency import Foundation +#else +import Foundation +#endif public struct Request: Sendable { public var method: String