Skip to content

Commit 840a537

Browse files
Issue 18 - Adding Upload Completion Handler for Handling Custom/Error… (#19)
* Issue 18 - Adding Upload Completion Handler for Handling Custom/Error Responses * [Issue 18] Update Register APIs and Documentation for Uploads * [Issue 18] Update Upload Unit Tests
1 parent 8093c39 commit 840a537

File tree

5 files changed

+92
-16
lines changed

5 files changed

+92
-16
lines changed

Sources/YNetwork/NetworkManager/NetworkManager+URLSessionTaskDelegate.swift

+12-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Foundation
1010

1111
// MARK: - URLSessionTaskDelegate
1212

13-
extension NetworkManager: URLSessionTaskDelegate {
13+
extension NetworkManager: URLSessionTaskDelegate, URLSessionDataDelegate {
1414
/// :nodoc:
1515
public func urlSession(
1616
_ session: URLSession,
@@ -40,4 +40,15 @@ extension NetworkManager: URLSessionTaskDelegate {
4040
fileUpload.urlSession(session, task: task, didCompleteWithError: error)
4141
}
4242
}
43+
44+
/// :nodoc:
45+
public func urlSession(
46+
_ session: URLSession,
47+
dataTask: URLSessionDataTask,
48+
didReceive data: Data
49+
) {
50+
if dataTask is URLSessionUploadTask {
51+
fileUpload.urlSession(session, dataTask: dataTask, didReceive: data)
52+
}
53+
}
4354
}

Sources/YNetwork/NetworkManager/NetworkManager.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,12 @@ open class NetworkManager: NSObject {
161161
/// - Parameters:
162162
/// - request: the upload network request to submit
163163
/// - progress: progress handler (will be called back on main thread)
164+
/// - completionHandler: file upload handler (will be called back on URLSession background thread).
164165
/// - Returns: a cancelable upload task if one was able to be created, otherwise nil if no task was issued
165166
@discardableResult open func submitBackgroundUpload(
166167
_ request: NetworkRequest,
167-
progress: ProgressHandler? = nil
168+
progress: ProgressHandler? = nil,
169+
completionHandler: FileUploadHandler? = nil
168170
) -> Cancelable? {
169171
guard let urlRequest = try? buildUrlRequest(request: request) else { return nil }
170172

@@ -178,7 +180,7 @@ open class NetworkManager: NSObject {
178180

179181
// creating the upload task copies the file
180182
let task = try? configuration?.networkEngine.submitBackgroundUpload(urlRequest, fileUrl: localURL)
181-
fileUpload.register(task, progress: progress)
183+
fileUpload.registerUpload(task, progress: progress, completion: completionHandler)
182184
return task
183185
}
184186

Sources/YNetwork/NetworkManager/Progress/FileProgress.swift

+58-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ public typealias Percentage = Double
1616
/// Guaranteed to be called back on the main thread
1717
public typealias ProgressHandler = (Percentage) -> Void
1818

19+
/// Asynchronous completion handler that is called when a response is received for an upload
20+
public typealias FileUploadHandler = (Data) -> Void
21+
22+
/// Asynchronous cancellation handler that is called when an upload request is cancelled
23+
/// This can be used with the cancellationHandler attribute of the Progress object associated with the upload task
24+
public typealias CancellationHandler = () -> Void
25+
1926
/// Asynchronous completion handler that reports the status of request.
2027
///
2128
/// Guaranteed to be called back on a background thread (because the system erases the temporary file)
@@ -29,6 +36,7 @@ public typealias FileDownloadHandler = (Result<URL, Error>) -> Void
2936
internal class FileProgress: NSObject {
3037
/// Stores the progress handlers to be called, keyed by unique task identifier
3138
private var progressHandlersByTaskID: [Int: ProgressHandler] = [:]
39+
private var uploadHandlerByTaskID: [Int: FileUploadHandler] = [:]
3240
private var downloadHandlersByTaskID: [Int: FileDownloadHandler] = [:]
3341

3442
/// Updates the progress handler for the specified task with the percentage value
@@ -41,6 +49,17 @@ internal class FileProgress: NSObject {
4149
progressHandler(percent)
4250
}
4351
}
52+
53+
/// Invokes the completion handler for the specified task with the response data
54+
/// - Parameters:
55+
/// - data: the response data that can be decoded for custom responses such as error messages
56+
/// - taskIdentifier: unique task identifier
57+
func receive(data: Data, forKey taskIdentifier: Int) {
58+
guard let completionHandler = uploadHandlerByTaskID[taskIdentifier] else { return }
59+
DispatchQueue.main.async {
60+
completionHandler(data)
61+
}
62+
}
4463

4564
/// Updates the request status for the specified task with the file URL
4665
/// - Parameters:
@@ -53,16 +72,32 @@ internal class FileProgress: NSObject {
5372
completionhandler(result)
5473
}
5574

56-
/// Registers a data task for file progress.
75+
/// Registers a data task for file progress of either an upload or download.
5776
/// - Parameters:
5877
/// - cancelable: optional cancelable task
5978
/// - progress: optional progress handler
60-
func register(_ cancelable: Cancelable?, progress: ProgressHandler?) {
79+
func registerProgress(
80+
_ cancelable: Cancelable?,
81+
progress: ProgressHandler?
82+
) {
6183
guard let task = cancelable as? URLSessionTask,
6284
let progress = progress else { return }
6385
progressHandlersByTaskID[task.taskIdentifier] = progress
6486
}
6587

88+
/// Registers the data task with a completion handler to be called when the response to the upload is received.
89+
/// - Parameters:
90+
/// - cancelable: optional cancelable task
91+
/// - completion: optional completion handler
92+
func registerCompletion(
93+
_ cancelable: Cancelable?,
94+
completion: FileUploadHandler?
95+
) {
96+
guard let task = cancelable as? URLSessionTask,
97+
let completion = completion else { return }
98+
uploadHandlerByTaskID[task.taskIdentifier] = completion
99+
}
100+
66101
/// Registers a data task for file progress.
67102
/// - Parameters:
68103
/// - cancelable: optional cancelable task
@@ -73,17 +108,37 @@ internal class FileProgress: NSObject {
73108
progress: ProgressHandler?,
74109
handler: @escaping FileDownloadHandler
75110
) {
76-
register(cancelable, progress: progress)
111+
registerProgress(cancelable, progress: progress)
77112
guard let task = cancelable as? URLSessionTask else { return }
78113
downloadHandlersByTaskID[task.taskIdentifier] = handler
79114
}
115+
116+
/// Registers a data task for file upload progress and completion.
117+
/// - Parameters:
118+
/// - cancelable: optional cancelable task
119+
/// - progress: optional progress handler
120+
/// - completion: optional completion handler
121+
func registerUpload(
122+
_ cancelable: Cancelable?,
123+
progress: ProgressHandler?,
124+
completion: FileUploadHandler?
125+
) {
126+
registerProgress(cancelable, progress: progress)
127+
registerCompletion(cancelable, completion: completion)
128+
}
80129

81130
/// Unregisters a data task for file progress
82131
/// - Parameter taskIdentifier: unique task identifier
83132
func unregister(forKey taskIdentifier: Int) {
84133
progressHandlersByTaskID.removeValue(forKey: taskIdentifier)
85134
downloadHandlersByTaskID.removeValue(forKey: taskIdentifier)
86135
}
136+
137+
/// Unregisters a completion handler, should be called once the final response is received
138+
/// - Parameter taskIdentifier: unique task identifier
139+
func unregisterUploadCompletion(forKey taskIdentifier: Int) {
140+
uploadHandlerByTaskID.removeValue(forKey: taskIdentifier)
141+
}
87142

88143
func checkResponseForError(task: URLSessionTask) -> Error? {
89144
guard let httpResponse = task.response as? HTTPURLResponse else {

Sources/YNetwork/NetworkManager/Progress/FileUploadProgress.swift

+11-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Foundation
1515
/// to optionally track progress for large file upload tasks.
1616
internal class FileUploadProgress: FileProgress { }
1717

18-
extension FileUploadProgress: URLSessionTaskDelegate {
18+
extension FileUploadProgress: URLSessionTaskDelegate, URLSessionDataDelegate {
1919
func urlSession(
2020
_ session: URLSession,
2121
task: URLSessionTask,
@@ -31,4 +31,14 @@ extension FileUploadProgress: URLSessionTaskDelegate {
3131
// clean up the task now that we're finished with it
3232
unregister(forKey: task.taskIdentifier)
3333
}
34+
35+
public func urlSession(
36+
_ session: URLSession,
37+
dataTask: URLSessionDataTask,
38+
didReceive data: Data
39+
) {
40+
// clean up the task now that the final response for the upload was received
41+
receive(data: data, forKey: dataTask.taskIdentifier)
42+
unregisterUploadCompletion(forKey: dataTask.taskIdentifier)
43+
}
3444
}

Tests/YNetworkTests/NetworkManager/NetworkManagerUploadTests.swift

+7-9
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ final class NetworkManagerUploadTests: XCTestCase {
7474

7575
XCTAssertNil(sut.receivedError)
7676

77-
let task = try XCTUnwrap(sut.submitBackgroundUpload(request) { _ in } as? URLSessionTask)
77+
let task = try XCTUnwrap(sut.submitBackgroundUpload(request, completionHandler: { _ in }) as? URLSessionTask)
7878
task.cancel() // this will make it fail
7979
task.resume() // resume it
8080

@@ -90,7 +90,7 @@ final class NetworkManagerUploadTests: XCTestCase {
9090
XCTAssertNil(sut.receivedError)
9191

9292
URLProtocolStub.appendStub(.failure(NetworkError.invalidResponse), type: .upload)
93-
let task = sut.submitBackgroundUpload(request) { _ in }
93+
let task = sut.submitBackgroundUpload(request, completionHandler: { _ in })
9494

9595
XCTAssertNotNil(task)
9696

@@ -154,7 +154,7 @@ final class NetworkManagerUploadTests: XCTestCase {
154154
let sut = NetworkManager()
155155

156156
// Given we submit a request without first configuring the network manager
157-
let task = sut.submitBackgroundUpload(request) { _ in }
157+
let task = sut.submitBackgroundUpload(request, completionHandler: { _ in })
158158

159159
// We don't expect a task to be returned
160160
XCTAssertNil(task)
@@ -209,19 +209,17 @@ private final class NetworkManagerSpy: NetworkManager {
209209
self.fulfill()
210210
}
211211
}
212+
213+
override func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
214+
self.receivedData = data
215+
}
212216

213217
func fulfill() {
214218
expectation?.fulfill()
215219
expectation = nil
216220
}
217221
}
218222

219-
extension NetworkManagerSpy: URLSessionDataDelegate {
220-
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
221-
self.receivedData = data
222-
}
223-
}
224-
225223
public enum NetworkSpyError: Error {
226224
case cancelled
227225
}

0 commit comments

Comments
 (0)