Skip to content

Commit b18dd6a

Browse files
committed
Allow cancellation for network requests
1 parent 384ec38 commit b18dd6a

File tree

6 files changed

+97
-14
lines changed

6 files changed

+97
-14
lines changed

SwiftIpfsApi.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
1F47CB9E244988B9006F251C /* CancellableRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F47CB9D244988B9006F251C /* CancellableRequest.swift */; };
11+
1F47CBA024498C67006F251C /* CancellableRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F47CB9F24498C67006F251C /* CancellableRequestTests.swift */; };
1012
AB0A3AB81BD6705B0090C97A /* SwiftIpfsApi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0A3AAD1BD6705B0090C97A /* SwiftIpfsApi.framework */; };
1113
AB0A3ABD1BD6705B0090C97A /* SwiftIpfsApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB0A3ABC1BD6705B0090C97A /* SwiftIpfsApiTests.swift */; };
1214
AB0A3AC81BD671320090C97A /* MerkleNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB0A3AC71BD671320090C97A /* MerkleNode.swift */; };
@@ -43,6 +45,8 @@
4345
/* End PBXContainerItemProxy section */
4446

4547
/* Begin PBXFileReference section */
48+
1F47CB9D244988B9006F251C /* CancellableRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellableRequest.swift; sourceTree = "<group>"; };
49+
1F47CB9F24498C67006F251C /* CancellableRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellableRequestTests.swift; sourceTree = "<group>"; };
4650
AB0A3AAD1BD6705B0090C97A /* SwiftIpfsApi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftIpfsApi.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4751
AB0A3AB21BD6705B0090C97A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
4852
AB0A3AB71BD6705B0090C97A /* SwiftIpfsApiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftIpfsApiTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -120,6 +124,7 @@
120124
AB0A3AB21BD6705B0090C97A /* Info.plist */,
121125
AB0A3AC71BD671320090C97A /* MerkleNode.swift */,
122126
ABEDF91D1BF0BE58007A1B2B /* JsonType.swift */,
127+
1F47CB9D244988B9006F251C /* CancellableRequest.swift */,
123128
AB490F8B1BF232F3005C5F57 /* Subcommands */,
124129
);
125130
path = SwiftIpfsApi;
@@ -129,6 +134,7 @@
129134
isa = PBXGroup;
130135
children = (
131136
AB0A3ABC1BD6705B0090C97A /* SwiftIpfsApiTests.swift */,
137+
1F47CB9F24498C67006F251C /* CancellableRequestTests.swift */,
132138
AB0A3ABE1BD6705B0090C97A /* Info.plist */,
133139
);
134140
path = SwiftIpfsApiTests;
@@ -302,6 +308,7 @@
302308
AB490F8A1BF232D4005C5F57 /* Refs.swift in Sources */,
303309
ABEDF91E1BF0BE58007A1B2B /* JsonType.swift in Sources */,
304310
AB490F9F1BF23516005C5F57 /* Config.swift in Sources */,
311+
1F47CB9E244988B9006F251C /* CancellableRequest.swift in Sources */,
305312
AB490F9D1BF234F1005C5F57 /* Diag.swift in Sources */,
306313
AB2C9EE01BD79F9B00BCCF01 /* HttpIo.swift in Sources */,
307314
AB490F881BF231F7005C5F57 /* Pin.swift in Sources */,
@@ -324,6 +331,7 @@
324331
isa = PBXSourcesBuildPhase;
325332
buildActionMask = 2147483647;
326333
files = (
334+
1F47CBA024498C67006F251C /* CancellableRequestTests.swift in Sources */,
327335
AB0A3ABD1BD6705B0090C97A /* SwiftIpfsApiTests.swift in Sources */,
328336
);
329337
runOnlyForDeploymentPostprocessing = 0;

SwiftIpfsApi/CancellableRequest.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// CancellableRequest.swift
3+
// SwiftIpfsApi
4+
//
5+
// Created by Marcel Voß on 17.04.20.
6+
// Copyright © 2020 Teo Sartori. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// A protocol that represents any network request that can be cancelled during execution.
12+
public protocol CancellableRequest {
13+
func cancel()
14+
}
15+
16+
/// A concrete type conforming to the `CancellableRequest` protocol that can be used for abstracting inner implementation
17+
/// details of the networking layer away and not leaking this kind of information to any integrators.
18+
struct CancellableDataTask: CancellableRequest {
19+
private let request: URLSessionDataTask
20+
21+
init(request: URLSessionDataTask) {
22+
self.request = request
23+
}
24+
25+
func cancel() {
26+
request.cancel()
27+
}
28+
}

SwiftIpfsApi/HttpIo.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ enum HttpIoError : Error {
1717

1818
public struct HttpIo : NetworkIo {
1919

20-
public func receiveFrom(_ source: String, completionHandler: @escaping (Data) throws -> Void) throws {
20+
public func receiveFrom(_ source: String, completionHandler: @escaping (Data) throws -> Void) throws -> CancellableRequest {
2121
guard let url = URL(string: source) else { throw HttpIoError.urlError("Invalid URL") }
2222
print("HttpIo receiveFrom url is \(url)")
23+
2324
let task = URLSession.shared.dataTask(with: url) {
2425
(data: Data?, response: URLResponse?, error: Error?) in
2526

@@ -35,14 +36,16 @@ public struct HttpIo : NetworkIo {
3536
print("Error ", error, "in completionHandler passed to fetchData ")
3637
}
3738
}
38-
39+
3940
task.resume()
41+
42+
return CancellableDataTask(request: task)
4043
}
4144

4245

4346
public func streamFrom( _ source: String,
4447
updateHandler: @escaping (Data, URLSessionDataTask) throws -> Bool,
45-
completionHandler: @escaping (AnyObject) throws -> Void) throws {
48+
completionHandler: @escaping (AnyObject) throws -> Void) throws -> CancellableRequest {
4649

4750
guard let url = URL(string: source) else { throw HttpIoError.urlError("Invalid URL") }
4851
let config = URLSessionConfiguration.default
@@ -51,23 +54,24 @@ public struct HttpIo : NetworkIo {
5154
let task = session.dataTask(with: url)
5255

5356
task.resume()
57+
return CancellableDataTask(request: task)
5458
}
5559

56-
public func sendTo(_ target: String, content: Data, completionHandler: @escaping (Data) -> Void) throws {
60+
public func sendTo(_ target: String, content: Data, completionHandler: @escaping (Data) -> Void) throws -> CancellableRequest {
5761

5862
var multipart = try Multipart(targetUrl: target, encoding: .utf8)
5963
multipart = try Multipart.addFilePart(multipart, fileName: nil , fileData: content)
60-
Multipart.finishMultipart(multipart, completionHandler: completionHandler)
64+
return Multipart.finishMultipart(multipart, completionHandler: completionHandler)
6165
}
6266

6367

64-
public func sendTo(_ target: String, filePath: String, completionHandler: @escaping (Data) -> Void) throws {
68+
public func sendTo(_ target: String, filePath: String, completionHandler: @escaping (Data) -> Void) throws -> CancellableRequest {
6569

6670
var multipart = try Multipart(targetUrl: target, encoding: .utf8)
6771

6872
multipart = try handle(oldMultipart: multipart, files: [filePath])
6973

70-
Multipart.finishMultipart(multipart, completionHandler: completionHandler)
74+
return Multipart.finishMultipart(multipart, completionHandler: completionHandler)
7175

7276
}
7377

SwiftIpfsApi/Multipart.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ extension Multipart {
141141
return oldMultipart
142142
}
143143

144-
public static func finishMultipart(_ multipart: Multipart, completionHandler: @escaping (Data) -> Void) {
144+
public static func finishMultipart(_ multipart: Multipart, completionHandler: @escaping (Data) -> Void) -> CancellableRequest {
145145

146146
let outString = "--" + multipart.boundary + "--" + lineFeed
147147

@@ -167,5 +167,7 @@ extension Multipart {
167167
}
168168

169169
task.resume()
170+
171+
return CancellableDataTask(request: task)
170172
}
171173
}

SwiftIpfsApi/NetworkIo.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@
1111
import Foundation
1212

1313
public protocol NetworkIo {
14-
15-
func receiveFrom(_ source: String, completionHandler: @escaping (Data) throws -> Void) throws
1614

17-
func streamFrom(_ source: String, updateHandler: @escaping (Data, URLSessionDataTask) throws -> Bool, completionHandler: @escaping (AnyObject) throws -> Void) throws
18-
19-
func sendTo(_ target: String, content: Data, completionHandler: @escaping (Data) -> Void) throws
15+
@discardableResult
16+
func receiveFrom(_ source: String, completionHandler: @escaping (Data) throws -> Void) throws -> CancellableRequest
17+
18+
@discardableResult
19+
func streamFrom(_ source: String,
20+
updateHandler: @escaping (Data, URLSessionDataTask) throws -> Bool,
21+
completionHandler: @escaping (AnyObject) throws -> Void) throws -> CancellableRequest
22+
23+
@discardableResult
24+
func sendTo(_ target: String, content: Data, completionHandler: @escaping (Data) -> Void) throws -> CancellableRequest
2025

2126
/// If we want to send location addressed content
22-
func sendTo(_ target: String, filePath: String, completionHandler: @escaping (Data) -> Void) throws
27+
@discardableResult
28+
func sendTo(_ target: String, filePath: String, completionHandler: @escaping (Data) -> Void) throws -> CancellableRequest
2329
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// CancellableRequestTests.swift
3+
// SwiftIpfsApiTests
4+
//
5+
// Created by Marcel Voß on 17.04.20.
6+
// Copyright © 2020 Teo Sartori. All rights reserved.
7+
//
8+
9+
import XCTest
10+
11+
class CancellableRequestTests: XCTestCase {
12+
13+
func testCancellableRequest() {
14+
let task = MockURLSessionDataTask()
15+
16+
let cancellationExpectation = expectation(description: "expected to cancel network request")
17+
18+
task.onCancel = {
19+
cancellationExpectation.fulfill()
20+
}
21+
22+
task.cancel()
23+
24+
waitForExpectations(timeout: 1.0)
25+
}
26+
27+
}
28+
29+
private class MockURLSessionDataTask: URLSessionDataTask {
30+
var onCancel: (() -> Void)?
31+
32+
override func cancel() {
33+
onCancel?()
34+
}
35+
}

0 commit comments

Comments
 (0)