-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #306 from ishkawa/feature/async-await
Support Swift Concurrency
- Loading branch information
Showing
5 changed files
with
177 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
#if compiler(>=5.5.2) && canImport(_Concurrency) | ||
|
||
import Foundation | ||
|
||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) | ||
public extension Session { | ||
/// Calls `response(for:callbackQueue:)` of `Session.shared`. | ||
/// | ||
/// - parameter request: The request to be sent. | ||
/// - parameter callbackQueue: The queue where the handler runs. If this parameters is `nil`, default `callbackQueue` of `Session` will be used. | ||
/// - returns: `Request.Response` | ||
static func response<Request: APIKit.Request>(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { | ||
return try await shared.response(for: request, callbackQueue: callbackQueue) | ||
} | ||
|
||
/// Convenience method to load `Request.Response` using an `Request`, creates and resumes an `SessionTask` internally. | ||
/// | ||
/// - parameter request: The request to be sent. | ||
/// - parameter callbackQueue: The queue where the handler runs. If this parameters is `nil`, default `callbackQueue` of `Session` will be used. | ||
/// - returns: `Request.Response` | ||
func response<Request: APIKit.Request>(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { | ||
let cancellationHandler = SessionTaskCancellationHandler() | ||
return try await withTaskCancellationHandler(operation: { | ||
return try await withCheckedThrowingContinuation { continuation in | ||
Task { | ||
let sessionTask = createSessionTask(request, callbackQueue: callbackQueue) { result in | ||
continuation.resume(with: result) | ||
} | ||
await cancellationHandler.register(with: sessionTask) | ||
if await cancellationHandler.isTaskCancelled { | ||
sessionTask?.cancel() | ||
} else { | ||
sessionTask?.resume() | ||
} | ||
} | ||
} | ||
}, onCancel: { | ||
Task { await cancellationHandler.cancel() } | ||
}) | ||
} | ||
} | ||
|
||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) | ||
private actor SessionTaskCancellationHandler { | ||
private var sessionTask: SessionTask? | ||
private(set) var isTaskCancelled = false | ||
|
||
func register(with task: SessionTask?) { | ||
guard !isTaskCancelled else { return } | ||
guard sessionTask == nil else { return } | ||
sessionTask = task | ||
} | ||
|
||
func cancel() { | ||
isTaskCancelled = true | ||
sessionTask?.cancel() | ||
} | ||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#if compiler(>=5.6.0) && canImport(_Concurrency) | ||
|
||
import XCTest | ||
import APIKit | ||
|
||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) | ||
final class ConcurrencyTests: XCTestCase { | ||
var adapter: TestSessionAdapter! | ||
var session: Session! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
adapter = TestSessionAdapter() | ||
session = Session(adapter: adapter) | ||
} | ||
|
||
func testSuccess() async throws { | ||
let dictionary = ["key": "value"] | ||
adapter.data = try XCTUnwrap(JSONSerialization.data(withJSONObject: dictionary, options: [])) | ||
|
||
let request = TestRequest() | ||
let value = try await session.response(for: request) | ||
XCTAssertEqual((value as? [String: String])?["key"], "value") | ||
} | ||
|
||
func testParseDataError() async throws { | ||
adapter.data = "{\"broken\": \"json}".data(using: .utf8, allowLossyConversion: false) | ||
|
||
let request = TestRequest() | ||
do { | ||
_ = try await session.response(for: request) | ||
XCTFail() | ||
} catch { | ||
let sessionError = try XCTUnwrap(error as? SessionTaskError) | ||
if case .responseError(let responseError as NSError) = sessionError { | ||
XCTAssertEqual(responseError.domain, NSCocoaErrorDomain) | ||
XCTAssertEqual(responseError.code, 3840) | ||
} else { | ||
XCTFail() | ||
} | ||
} | ||
} | ||
|
||
func testCancel() async throws { | ||
let request = TestRequest() | ||
|
||
let task = Task { | ||
do { | ||
_ = try await session.response(for: request) | ||
XCTFail() | ||
} catch { | ||
let sessionError = try XCTUnwrap(error as? SessionTaskError) | ||
if case .connectionError(let connectionError as NSError) = sessionError { | ||
XCTAssertEqual(connectionError.code, 0) | ||
XCTAssertTrue(Task.isCancelled) | ||
} else { | ||
XCTFail() | ||
} | ||
} | ||
} | ||
task.cancel() | ||
_ = try await task.value | ||
|
||
XCTAssertTrue(task.isCancelled) | ||
} | ||
} | ||
|
||
#endif |