Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update for Swift6 Concurrency #1

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions APIKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
7F7048F31D9D8A1F003C99F6 /* URLEncodedSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F7048F21D9D8A1F003C99F6 /* URLEncodedSerialization.swift */; };
7FA1690D1D9D8C80006C982B /* HTTPStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA1690C1D9D8C80006C982B /* HTTPStub.swift */; };
C5725F4B28D8C36500810D7C /* Concurrency.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5725F4A28D8C36500810D7C /* Concurrency.swift */; };
C5AC78842C535379008EDBDC /* UncheckedSendableBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5AC78832C535379008EDBDC /* UncheckedSendableBox.swift */; };
C5AC78862C537710008EDBDC /* ErrorObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5AC78852C537710008EDBDC /* ErrorObject.swift */; };
C5B144D828D8D7DC00E30ECD /* ConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B144D728D8D7DC00E30ECD /* ConcurrencyTests.swift */; };
C5FF1DC128A80FFD0059573D /* test.json in Resources */ = {isa = PBXBuildFile; fileRef = C5FF1DC028A80FFD0059573D /* test.json */; };
ECA831481DE4DDBF004EB1B5 /* ProtobufDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */; };
Expand Down Expand Up @@ -131,6 +133,8 @@
7F8ECDFD1B6A799E00234E04 /* Demo.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Demo.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
7FA1690C1D9D8C80006C982B /* HTTPStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPStub.swift; sourceTree = "<group>"; };
C5725F4A28D8C36500810D7C /* Concurrency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Concurrency.swift; sourceTree = "<group>"; };
C5AC78832C535379008EDBDC /* UncheckedSendableBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UncheckedSendableBox.swift; sourceTree = "<group>"; };
C5AC78852C537710008EDBDC /* ErrorObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorObject.swift; sourceTree = "<group>"; };
C5B144D728D8D7DC00E30ECD /* ConcurrencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrencyTests.swift; sourceTree = "<group>"; };
C5FF1DC028A80FFD0059573D /* test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = test.json; sourceTree = "<group>"; };
ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtobufDataParser.swift; path = Sources/APIKit/DataParser/ProtobufDataParser.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -315,6 +319,7 @@
7F7048CA1D9D89BE003C99F6 /* Request.swift */,
7F7048CB1D9D89BE003C99F6 /* Session.swift */,
7F7048CC1D9D89BE003C99F6 /* Unavailable.swift */,
C5AC78832C535379008EDBDC /* UncheckedSendableBox.swift */,
C5725F4928D8C36500810D7C /* Concurrency */,
0969AE0D259DEC3C00C498AF /* Combine */,
7F85FB8B1C9D317300CEE132 /* SessionAdapter */,
Expand Down Expand Up @@ -352,6 +357,7 @@
7F7048EE1D9D8A12003C99F6 /* SessionTaskError.swift */,
7F7048EC1D9D8A12003C99F6 /* RequestError.swift */,
7F7048ED1D9D8A12003C99F6 /* ResponseError.swift */,
C5AC78852C537710008EDBDC /* ErrorObject.swift */,
);
name = Error;
path = APIKit/Error;
Expand Down Expand Up @@ -514,8 +520,10 @@
7F7048E31D9D89FB003C99F6 /* MultipartFormDataBodyParameters.swift in Sources */,
7F7048F01D9D8A12003C99F6 /* ResponseError.swift in Sources */,
7F7048EA1D9D8A08003C99F6 /* JSONDataParser.swift in Sources */,
C5AC78862C537710008EDBDC /* ErrorObject.swift in Sources */,
7F7048D21D9D89BE003C99F6 /* Session.swift in Sources */,
7F7048E01D9D89FB003C99F6 /* Data+InputStream.swift in Sources */,
C5AC78842C535379008EDBDC /* UncheckedSendableBox.swift in Sources */,
7F7048DF1D9D89FB003C99F6 /* BodyParameters.swift in Sources */,
7F7048E21D9D89FB003C99F6 /* JSONBodyParameters.swift in Sources */,
C5725F4B28D8C36500810D7C /* Concurrency.swift in Sources */,
Expand Down Expand Up @@ -578,9 +586,14 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
TVOS_DEPLOYMENT_TARGET = 16.0;
WATCHOS_DEPLOYMENT_TARGET = 9.0;
};
name = Debug;
};
Expand All @@ -590,9 +603,14 @@
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.0;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_STRICT_CONCURRENCY = complete;
TVOS_DEPLOYMENT_TARGET = 16.0;
WATCHOS_DEPLOYMENT_TARGET = 9.0;
};
name = Release;
};
Expand All @@ -601,6 +619,9 @@
baseConfigurationReference = 141F12401C1C9EA30026D415 /* Tests.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Tests/APIKitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
TVOS_DEPLOYMENT_TARGET = 16.0;
};
name = Debug;
};
Expand All @@ -609,7 +630,10 @@
baseConfigurationReference = 141F12401C1C9EA30026D415 /* Tests.xcconfig */;
buildSettings = {
INFOPLIST_FILE = Tests/APIKitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
MACOSX_DEPLOYMENT_TARGET = 13.0;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TVOS_DEPLOYMENT_TARGET = 16.0;
};
name = Release;
};
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PackageDescription
let package = Package(
name: "APIKit",
platforms: [
.macOS(.v10_10), .iOS(.v9), .tvOS(.v9), .watchOS(.v2)
.macOS(.v13), .iOS(.v16), .tvOS(.v16), .watchOS(.v9)
],
products: [
.library(name: "APIKit", targets: ["APIKit"]),
Expand Down
4 changes: 2 additions & 2 deletions Sources/APIKit/BodyParameters/Data+InputStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation

enum InputStreamError: Error {
case invalidDataCapacity(Int)
case unreadableStream(InputStream)
case unreadableStream(Error?)
}

extension Data {
Expand All @@ -22,7 +22,7 @@ extension Data {
data.append(buffer, count: readSize)

case let x where x < 0:
throw InputStreamError.unreadableStream(inputStream)
throw InputStreamError.unreadableStream(inputStream.streamError)

default:
break
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import UniformTypeIdentifiers

#if os(iOS) || os(watchOS) || os(tvOS)
import MobileCoreServices
Expand Down Expand Up @@ -60,7 +61,7 @@ public extension MultipartFormDataBodyParameters {
/// Part represents single part of multipart/form-data.
struct Part {
public enum Error: Swift.Error {
case illegalValue(Any)
case illegalValue(ErrorObject<Any>)
case illegalFileURL(URL)
case cannotGetFileSize(URL)
}
Expand All @@ -76,7 +77,7 @@ public extension MultipartFormDataBodyParameters {
/// If `mimeType` or `fileName` are `nil`, the fields will be omitted.
public init(value: Any, name: String, mimeType: String? = nil, fileName: String? = nil, encoding: String.Encoding = .utf8) throws {
guard let data = String(describing: value).data(using: encoding) else {
throw Error.illegalValue(value)
throw Error.illegalValue(.init(value: value))
}

self.inputStream = InputStream(data: data)
Expand Down Expand Up @@ -111,9 +112,8 @@ public extension MultipartFormDataBodyParameters {
throw Error.cannotGetFileSize(fileURL)
}

let detectedMimeType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileURL.pathExtension as CFString, nil)
.map { $0.takeRetainedValue() }
.flatMap { UTTypeCopyPreferredTagWithClass($0, kUTTagClassMIMEType)?.takeRetainedValue() }
let detectedMimeType = UTType(filenameExtension: fileURL.pathExtension)
.flatMap { $0.preferredMIMEType }
.map { $0 as String }

self.inputStream = inputStream
Expand Down
4 changes: 2 additions & 2 deletions Sources/APIKit/CallbackQueue.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// `CallbackQueue` represents queue where `handler` of `Session.send(_:handler:)` runs.
public enum CallbackQueue {
public enum CallbackQueue: Sendable {
/// Dispatches callback closure on main queue asynchronously.
case main

Expand All @@ -14,7 +14,7 @@ public enum CallbackQueue {
/// Dispatches callback closure on associated dispatch queue.
case dispatchQueue(DispatchQueue)

public func execute(closure: @escaping () -> Void) {
public func execute(closure: @escaping @Sendable () -> Void) {
switch self {
case .main:
DispatchQueue.main.async {
Expand Down
13 changes: 7 additions & 6 deletions Sources/APIKit/Combine/Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ public struct SessionTaskPublisher<Request: APIKit.Request>: Publisher {
downstream: subscriber))
}

private final class SessionTaskSubscription<Request: APIKit.Request, Downstream: Subscriber>: Subscription where Request.Response == Downstream.Input, Downstream.Failure == Failure {
private final class SessionTaskSubscription<Req: APIKit.Request, Downstream: Subscriber>: Subscription where Req.Response == Downstream.Input, Downstream.Failure == Failure {

private let request: Request
private let request: Req
private let session: Session
private let callbackQueue: CallbackQueue?
private var downstream: Downstream?
private var task: SessionTask?

init(request: Request, session: Session, callbackQueue: CallbackQueue?, downstream: Downstream) {
init(request: Req, session: Session, callbackQueue: CallbackQueue?, downstream: Downstream) {
self.request = request
self.session = session
self.callbackQueue = callbackQueue
Expand All @@ -47,13 +47,14 @@ public struct SessionTaskPublisher<Request: APIKit.Request>: Publisher {
assert(demand > 0)
guard let downstream = self.downstream else { return }
self.downstream = nil
let ds = UncheckedSendableBox(value: downstream)
task = session.send(request, callbackQueue: callbackQueue) { result in
switch result {
case .success(let response):
_ = downstream.receive(response)
downstream.receive(completion: .finished)
_ = ds.value.receive(response)
ds.value.receive(completion: .finished)
case .failure(let error):
downstream.receive(completion: .failure(error))
ds.value.receive(completion: .failure(error))
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/APIKit/Concurrency/Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public extension Session {
let cancellationHandler = SessionTaskCancellationHandler()
return try await withTaskCancellationHandler(operation: {
return try await withCheckedThrowingContinuation { continuation in
let sessionTask = createSessionTask(request, callbackQueue: callbackQueue) { result in
continuation.resume(with: result)
}
Task {
let sessionTask = createSessionTask(request, callbackQueue: callbackQueue) { result in
continuation.resume(with: result)
}
await cancellationHandler.register(with: sessionTask)
if await cancellationHandler.isTaskCancelled {
sessionTask?.cancel()
Expand Down
7 changes: 7 additions & 0 deletions Sources/APIKit/Error/ErrorObject.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public final class ErrorObject<Value>: @unchecked Sendable {
public let value: Value

public init(value: Value) {
self.value = value
}
}
2 changes: 1 addition & 1 deletion Sources/APIKit/Error/ResponseError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ public enum ResponseError: Error {
case unacceptableStatusCode(Int)

/// Indicates `Any` that represents the response is unexpected.
case unexpectedObject(Any)
case unexpectedObject(ErrorObject<Any>)
}
2 changes: 1 addition & 1 deletion Sources/APIKit/HTTPMethod.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation

/// `HTTPMethod` represents HTTP methods.
public enum HTTPMethod: String {
public enum HTTPMethod: String, Sendable {
case get = "GET"
case post = "POST"
case put = "PUT"
Expand Down
12 changes: 6 additions & 6 deletions Sources/APIKit/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
/// - `var method: HTTPMethod`
/// - `var path: String`
/// - `func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response`
public protocol Request {
public protocol Request: Sendable {
/// The response type associated with the request type.
associatedtype Response

Expand All @@ -23,12 +23,12 @@ public protocol Request {
/// The convenience property for `queryParameters` and `bodyParameters`. If the implementation of
/// `queryParameters` and `bodyParameters` are not provided, the values for them will be computed
/// from this property depending on `method`.
var parameters: Any? { get }
var parameters: Sendable? { get }

/// The actual parameters for the URL query. The values of this property will be escaped using `URLEncodedSerialization`.
/// If this property is not implemented and `method.prefersQueryParameter` is `true`, the value of this property
/// will be computed from `parameters`.
var queryParameters: [String: Any]? { get }
var queryParameters: [String: Sendable]? { get }

/// The actual parameters for the HTTP body. If this property is not implemented and `method.prefersQueryParameter` is `false`,
/// the value of this property will be computed from `parameters` using `JSONBodyParameters`.
Expand Down Expand Up @@ -61,12 +61,12 @@ public protocol Request {
}

public extension Request {
var parameters: Any? {
var parameters: Sendable? {
return nil
}

var queryParameters: [String: Any]? {
guard let parameters = parameters as? [String: Any], method.prefersQueryParameters else {
var queryParameters: [String: Sendable]? {
guard let parameters = parameters as? [String: Sendable], method.prefersQueryParameters else {
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/APIKit/Serializations/URLEncodedSerialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public final class URLEncodedSerialization {
public enum Error: Swift.Error {
case cannotGetStringFromData(Data, String.Encoding)
case cannotGetDataFromString(String, String.Encoding)
case cannotCastObjectToDictionary(Any)
case cannotCastObjectToDictionary(ErrorObject<Any>)
case invalidFormatString(String)
}

Expand Down Expand Up @@ -76,7 +76,7 @@ public final class URLEncodedSerialization {
/// - Throws: URLEncodedSerialization.Error
public static func data(from object: Any, encoding: String.Encoding) throws -> Data {
guard let dictionary = object as? [String: Any] else {
throw Error.cannotCastObjectToDictionary(object)
throw Error.cannotCastObjectToDictionary(.init(value: object))
}

let string = self.string(from: dictionary)
Expand Down
Loading