From 26a94b0721c25ae5871bbd733b8740735dde4bf7 Mon Sep 17 00:00:00 2001 From: Econa77 Date: Mon, 7 Feb 2022 02:28:46 +0900 Subject: [PATCH 1/4] Try Swift Concurrency --- APIKit.xcodeproj/project.pbxproj | 13 ++++++ Sources/APIKit/Concurrency/Concurrency.swift | 45 ++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 Sources/APIKit/Concurrency/Concurrency.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index 16ded58..c3c90aa 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 7F7048F11D9D8A12003C99F6 /* SessionTaskError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F7048EE1D9D8A12003C99F6 /* SessionTaskError.swift */; }; 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 */; }; C5FF1DC128A80FFD0059573D /* test.json in Resources */ = {isa = PBXBuildFile; fileRef = C5FF1DC028A80FFD0059573D /* test.json */; }; ECA831481DE4DDBF004EB1B5 /* ProtobufDataParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */; }; ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA831491DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift */; }; @@ -128,6 +129,7 @@ 7F7048F21D9D8A1F003C99F6 /* URLEncodedSerialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = URLEncodedSerialization.swift; path = Sources/APIKit/Serializations/URLEncodedSerialization.swift; sourceTree = SOURCE_ROOT; }; 7F8ECDFD1B6A799E00234E04 /* Demo.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Demo.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7FA1690C1D9D8C80006C982B /* HTTPStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPStub.swift; sourceTree = ""; }; + C5725F4A28D8C36500810D7C /* Concurrency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Concurrency.swift; sourceTree = ""; }; C5FF1DC028A80FFD0059573D /* test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = test.json; sourceTree = ""; }; ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtobufDataParser.swift; path = Sources/APIKit/DataParser/ProtobufDataParser.swift; sourceTree = SOURCE_ROOT; }; ECA831491DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtobufDataParserTests.swift; sourceTree = ""; }; @@ -310,6 +312,7 @@ 7F7048CA1D9D89BE003C99F6 /* Request.swift */, 7F7048CB1D9D89BE003C99F6 /* Session.swift */, 7F7048CC1D9D89BE003C99F6 /* Unavailable.swift */, + C5725F4928D8C36500810D7C /* Concurrency */, 0969AE0D259DEC3C00C498AF /* Combine */, 7F85FB8B1C9D317300CEE132 /* SessionAdapter */, 7F18BD0D1C972C38003A31DF /* BodyParameters */, @@ -364,6 +367,15 @@ path = APIKit/DataParser; sourceTree = ""; }; + C5725F4928D8C36500810D7C /* Concurrency */ = { + isa = PBXGroup; + children = ( + C5725F4A28D8C36500810D7C /* Concurrency.swift */, + ); + name = Concurrency; + path = APIKit/Concurrency; + sourceTree = ""; + }; C5FF1DBF28A80FFD0059573D /* Resources */ = { isa = PBXGroup; children = ( @@ -495,6 +507,7 @@ 7F7048E01D9D89FB003C99F6 /* Data+InputStream.swift in Sources */, 7F7048DF1D9D89FB003C99F6 /* BodyParameters.swift in Sources */, 7F7048E21D9D89FB003C99F6 /* JSONBodyParameters.swift in Sources */, + C5725F4B28D8C36500810D7C /* Concurrency.swift in Sources */, 7F7048D61D9D89F2003C99F6 /* SessionAdapter.swift in Sources */, 7F7048EF1D9D8A12003C99F6 /* RequestError.swift in Sources */, 7F7048E91D9D8A08003C99F6 /* FormURLEncodedDataParser.swift in Sources */, diff --git a/Sources/APIKit/Concurrency/Concurrency.swift b/Sources/APIKit/Concurrency/Concurrency.swift new file mode 100644 index 0000000..ca9e523 --- /dev/null +++ b/Sources/APIKit/Concurrency/Concurrency.swift @@ -0,0 +1,45 @@ +#if compiler(>=5.5.2) && canImport(_Concurrency) + +import Foundation + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +private actor SessionTaskActor { + private weak var sessionTask: SessionTask? + + func send(_ sessionTask: SessionTask?) { + self.sessionTask = sessionTask + } + + func cancel() { + sessionTask?.cancel() + } +} + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +public extension Session { + static func response(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { + return try await shared.response(for: request, callbackQueue: callbackQueue) + } + + func response(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { + let sessionTaskActor = SessionTaskActor() + return try await withTaskCancellationHandler(operation: { + return try await withUnsafeThrowingContinuation { continuation in + Task { + await sessionTaskActor.send(send(request, callbackQueue: callbackQueue) { result in + switch result { + case .success(let response): + continuation.resume(returning: response) + case .failure(let error): + continuation.resume(throwing: error) + } + }) + } + } + }, onCancel: { + Task { await sessionTaskActor.cancel() } + }) + } +} + +#endif From eebbc027901b450cb4522fd403f669a0b02f78d8 Mon Sep 17 00:00:00 2001 From: Econa77 Date: Tue, 20 Sep 2022 02:00:10 +0900 Subject: [PATCH 2/4] Rename SessionTask Actor to SessionTaskCancellationHandler --- Sources/APIKit/Concurrency/Concurrency.swift | 37 +++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/Sources/APIKit/Concurrency/Concurrency.swift b/Sources/APIKit/Concurrency/Concurrency.swift index ca9e523..03866e9 100644 --- a/Sources/APIKit/Concurrency/Concurrency.swift +++ b/Sources/APIKit/Concurrency/Concurrency.swift @@ -2,19 +2,6 @@ import Foundation -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -private actor SessionTaskActor { - private weak var sessionTask: SessionTask? - - func send(_ sessionTask: SessionTask?) { - self.sessionTask = sessionTask - } - - func cancel() { - sessionTask?.cancel() - } -} - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public extension Session { static func response(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { @@ -22,24 +9,40 @@ public extension Session { } func response(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { - let sessionTaskActor = SessionTaskActor() + let cancellationHandler = SessionTaskCancellationHandler() return try await withTaskCancellationHandler(operation: { return try await withUnsafeThrowingContinuation { continuation in Task { - await sessionTaskActor.send(send(request, callbackQueue: callbackQueue) { result in + let sessionTask = send(request, callbackQueue: callbackQueue) { result in switch result { case .success(let response): continuation.resume(returning: response) case .failure(let error): continuation.resume(throwing: error) } - }) + } + await cancellationHandler.register(with: sessionTask) } } }, onCancel: { - Task { await sessionTaskActor.cancel() } + Task { await cancellationHandler.cancel() } }) } } +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +private actor SessionTaskCancellationHandler { + private var sessionTask: SessionTask? + + func register(with task: SessionTask?) { + guard sessionTask == nil else { return } + sessionTask = task + } + + func cancel() { + sessionTask?.cancel() + sessionTask = nil + } +} + #endif From c09b881350d7bfe9642e584ad5b611fc6b6a765c Mon Sep 17 00:00:00 2001 From: Econa77 Date: Tue, 20 Sep 2022 03:29:26 +0900 Subject: [PATCH 3/4] Add concurrency tests --- APIKit.xcodeproj/project.pbxproj | 12 ++++ Sources/APIKit/Combine/Combine.swift | 1 + Sources/APIKit/Concurrency/Concurrency.swift | 23 +++++-- Sources/APIKit/Error/SessionTaskError.swift | 3 + .../Concurrency/ConcurrencyTests.swift | 68 +++++++++++++++++++ 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 Tests/APIKitTests/Concurrency/ConcurrencyTests.swift diff --git a/APIKit.xcodeproj/project.pbxproj b/APIKit.xcodeproj/project.pbxproj index c3c90aa..e460ab5 100644 --- a/APIKit.xcodeproj/project.pbxproj +++ b/APIKit.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ 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 */; }; + 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 */; }; ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA831491DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift */; }; @@ -130,6 +131,7 @@ 7F8ECDFD1B6A799E00234E04 /* Demo.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Demo.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7FA1690C1D9D8C80006C982B /* HTTPStub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPStub.swift; sourceTree = ""; }; C5725F4A28D8C36500810D7C /* Concurrency.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Concurrency.swift; sourceTree = ""; }; + C5B144D728D8D7DC00E30ECD /* ConcurrencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrencyTests.swift; sourceTree = ""; }; C5FF1DC028A80FFD0059573D /* test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = test.json; sourceTree = ""; }; ECA831471DE4DDBF004EB1B5 /* ProtobufDataParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProtobufDataParser.swift; path = Sources/APIKit/DataParser/ProtobufDataParser.swift; sourceTree = SOURCE_ROOT; }; ECA831491DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProtobufDataParserTests.swift; sourceTree = ""; }; @@ -242,6 +244,7 @@ 7F698E451D9D680C00F1561D /* RequestTests.swift */, 7F698E491D9D680C00F1561D /* SessionCallbackQueueTests.swift */, 7F698E4A1D9D680C00F1561D /* SessionTests.swift */, + C5B144D628D8D7D000E30ECD /* Concurrency */, 0973EE33259E2DD000879BA2 /* Combine */, 7F698E3B1D9D680C00F1561D /* BodyParametersType */, 7F698E401D9D680C00F1561D /* DataParserType */, @@ -376,6 +379,14 @@ path = APIKit/Concurrency; sourceTree = ""; }; + C5B144D628D8D7D000E30ECD /* Concurrency */ = { + isa = PBXGroup; + children = ( + C5B144D728D8D7DC00E30ECD /* ConcurrencyTests.swift */, + ); + path = Concurrency; + sourceTree = ""; + }; C5FF1DBF28A80FFD0059573D /* Resources */ = { isa = PBXGroup; children = ( @@ -534,6 +545,7 @@ 7F698E581D9D680C00F1561D /* RequestTests.swift in Sources */, ECA8314A1DE4DEBE004EB1B5 /* ProtobufDataParserTests.swift in Sources */, 7F698E5E1D9D680C00F1561D /* TestRequest.swift in Sources */, + C5B144D828D8D7DC00E30ECD /* ConcurrencyTests.swift in Sources */, 7F698E601D9D680C00F1561D /* TestSessionTask.swift in Sources */, 0973EE35259E2DDC00879BA2 /* CombineTests.swift in Sources */, 7FA1690D1D9D8C80006C982B /* HTTPStub.swift in Sources */, diff --git a/Sources/APIKit/Combine/Combine.swift b/Sources/APIKit/Combine/Combine.swift index 0b4db3f..6e3223c 100644 --- a/Sources/APIKit/Combine/Combine.swift +++ b/Sources/APIKit/Combine/Combine.swift @@ -68,6 +68,7 @@ public struct SessionTaskPublisher: Publisher { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public extension Session { /// Calls `sessionTaskPublisher(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: A publisher that wraps a session task for the request. diff --git a/Sources/APIKit/Concurrency/Concurrency.swift b/Sources/APIKit/Concurrency/Concurrency.swift index 03866e9..2555fc9 100644 --- a/Sources/APIKit/Concurrency/Concurrency.swift +++ b/Sources/APIKit/Concurrency/Concurrency.swift @@ -4,22 +4,31 @@ 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(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(for request: Request, callbackQueue: CallbackQueue? = nil) async throws -> Request.Response { let cancellationHandler = SessionTaskCancellationHandler() return try await withTaskCancellationHandler(operation: { - return try await withUnsafeThrowingContinuation { continuation in + return try await withCheckedThrowingContinuation { continuation in + guard !Task.isCancelled else { + continuation.resume(throwing: SessionTaskError.taskAlreadyCancelledError) + return + } Task { let sessionTask = send(request, callbackQueue: callbackQueue) { result in - switch result { - case .success(let response): - continuation.resume(returning: response) - case .failure(let error): - continuation.resume(throwing: error) - } + continuation.resume(with: result) } await cancellationHandler.register(with: sessionTask) } diff --git a/Sources/APIKit/Error/SessionTaskError.swift b/Sources/APIKit/Error/SessionTaskError.swift index 864d928..c019714 100644 --- a/Sources/APIKit/Error/SessionTaskError.swift +++ b/Sources/APIKit/Error/SessionTaskError.swift @@ -10,4 +10,7 @@ public enum SessionTaskError: Error { /// Error while creating `Request.Response` from `(Data, URLResponse)`. case responseError(Error) + + /// Error when the `Task` in Concurrency was cancelled before execution. + case taskAlreadyCancelledError } diff --git a/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift b/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift new file mode 100644 index 0000000..39b51c2 --- /dev/null +++ b/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift @@ -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) + switch sessionError { + case .taskAlreadyCancelledError: + XCTAssertTrue(Task.isCancelled) + default: + XCTFail() + } + } + } + task.cancel() + _ = try await task.value + + XCTAssertTrue(task.isCancelled) + } +} + +#endif From 1695c58ace4b192f9e6b9c8fe007cb40cb6adc3b Mon Sep 17 00:00:00 2001 From: Econa77 Date: Sun, 2 Oct 2022 13:42:11 +0900 Subject: [PATCH 4/4] Optimize concurrency task cancellation --- Sources/APIKit/Concurrency/Concurrency.swift | 15 ++++--- Sources/APIKit/Error/SessionTaskError.swift | 3 -- Sources/APIKit/Session.swift | 42 ++++++++++--------- .../Concurrency/ConcurrencyTests.swift | 6 +-- 4 files changed, 35 insertions(+), 31 deletions(-) diff --git a/Sources/APIKit/Concurrency/Concurrency.swift b/Sources/APIKit/Concurrency/Concurrency.swift index 2555fc9..c0518e3 100644 --- a/Sources/APIKit/Concurrency/Concurrency.swift +++ b/Sources/APIKit/Concurrency/Concurrency.swift @@ -22,15 +22,16 @@ public extension Session { let cancellationHandler = SessionTaskCancellationHandler() return try await withTaskCancellationHandler(operation: { return try await withCheckedThrowingContinuation { continuation in - guard !Task.isCancelled else { - continuation.resume(throwing: SessionTaskError.taskAlreadyCancelledError) - return - } Task { - let sessionTask = send(request, callbackQueue: callbackQueue) { result in + 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: { @@ -42,15 +43,17 @@ public extension Session { @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() - sessionTask = nil } } diff --git a/Sources/APIKit/Error/SessionTaskError.swift b/Sources/APIKit/Error/SessionTaskError.swift index c019714..864d928 100644 --- a/Sources/APIKit/Error/SessionTaskError.swift +++ b/Sources/APIKit/Error/SessionTaskError.swift @@ -10,7 +10,4 @@ public enum SessionTaskError: Error { /// Error while creating `Request.Response` from `(Data, URLResponse)`. case responseError(Error) - - /// Error when the `Task` in Concurrency was cancelled before execution. - case taskAlreadyCancelledError } diff --git a/Sources/APIKit/Session.swift b/Sources/APIKit/Session.swift index 4406fca..85fb961 100644 --- a/Sources/APIKit/Session.swift +++ b/Sources/APIKit/Session.swift @@ -55,8 +55,30 @@ open class Session { /// - returns: The new session task. @discardableResult open func send(_ request: Request, callbackQueue: CallbackQueue? = nil, handler: @escaping (Result) -> Void = { _ in }) -> SessionTask? { - let callbackQueue = callbackQueue ?? self.callbackQueue + let task = createSessionTask(request, callbackQueue: callbackQueue, handler: handler) + task?.resume() + return task + } + + /// Cancels requests that passes the test. + /// - parameter requestType: The request type to cancel. + /// - parameter test: The test closure that determines if a request should be cancelled or not. + open func cancelRequests(with requestType: Request.Type, passingTest test: @escaping (Request) -> Bool = { _ in true }) { + adapter.getTasks { [weak self] tasks in + tasks + .filter { task in + if let request = self?.requestForTask(task) as Request? { + return test(request) + } else { + return false + } + } + .forEach { $0.cancel() } + } + } + internal func createSessionTask(_ request: Request, callbackQueue: CallbackQueue?, handler: @escaping (Result) -> Void) -> SessionTask? { + let callbackQueue = callbackQueue ?? self.callbackQueue let urlRequest: URLRequest do { urlRequest = try request.buildURLRequest() @@ -91,28 +113,10 @@ open class Session { } setRequest(request, forTask: task) - task.resume() return task } - /// Cancels requests that passes the test. - /// - parameter requestType: The request type to cancel. - /// - parameter test: The test closure that determines if a request should be cancelled or not. - open func cancelRequests(with requestType: Request.Type, passingTest test: @escaping (Request) -> Bool = { _ in true }) { - adapter.getTasks { [weak self] tasks in - tasks - .filter { task in - if let request = self?.requestForTask(task) as Request? { - return test(request) - } else { - return false - } - } - .forEach { $0.cancel() } - } - } - private func setRequest(_ request: Request, forTask task: SessionTask) { objc_setAssociatedObject(task, &taskRequestKey, request, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } diff --git a/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift b/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift index 39b51c2..3005233 100644 --- a/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift +++ b/Tests/APIKitTests/Concurrency/ConcurrencyTests.swift @@ -50,10 +50,10 @@ final class ConcurrencyTests: XCTestCase { XCTFail() } catch { let sessionError = try XCTUnwrap(error as? SessionTaskError) - switch sessionError { - case .taskAlreadyCancelledError: + if case .connectionError(let connectionError as NSError) = sessionError { + XCTAssertEqual(connectionError.code, 0) XCTAssertTrue(Task.isCancelled) - default: + } else { XCTFail() } }