4 // Copyright (c) 2014-2017 Alamofire Software Foundation (http://alamofire.org/)
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 /// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
28 /// executing all operations attached to the serial operation queue upon task completion.
29 open class TaskDelegate: NSObject {
33 /// The serial operation queue used to execute all operations after the task completes.
34 open let queue: OperationQueue
36 /// The data returned by the server.
37 public var data: Data? { return nil }
39 /// The error generated throughout the lifecyle of the task.
40 public var error: Error?
42 var task: URLSessionTask? {
44 taskLock.lock(); defer { taskLock.unlock() }
48 taskLock.lock(); defer { taskLock.unlock() }
53 var initialResponseTime: CFAbsoluteTime?
54 var credential: URLCredential?
55 var metrics: AnyObject? // URLSessionTaskMetrics
57 private var _task: URLSessionTask? {
61 private let taskLock = NSLock()
65 init(task: URLSessionTask?) {
69 let operationQueue = OperationQueue()
71 operationQueue.maxConcurrentOperationCount = 1
72 operationQueue.isSuspended = true
73 operationQueue.qualityOfService = .utility
81 initialResponseTime = nil
84 // MARK: URLSessionTaskDelegate
86 var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
87 var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
88 var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
89 var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
91 @objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
93 _ session: URLSession,
95 willPerformHTTPRedirection response: HTTPURLResponse,
96 newRequest request: URLRequest,
97 completionHandler: @escaping (URLRequest?) -> Void)
99 var redirectRequest: URLRequest? = request
101 if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
102 redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
105 completionHandler(redirectRequest)
108 @objc(URLSession:task:didReceiveChallenge:completionHandler:)
110 _ session: URLSession,
111 task: URLSessionTask,
112 didReceive challenge: URLAuthenticationChallenge,
113 completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
115 var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
116 var credential: URLCredential?
118 if let taskDidReceiveChallenge = taskDidReceiveChallenge {
119 (disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
120 } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
121 let host = challenge.protectionSpace.host
124 let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
125 let serverTrust = challenge.protectionSpace.serverTrust
127 if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
128 disposition = .useCredential
129 credential = URLCredential(trust: serverTrust)
131 disposition = .cancelAuthenticationChallenge
135 if challenge.previousFailureCount > 0 {
136 disposition = .rejectProtectionSpace
138 credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
140 if credential != nil {
141 disposition = .useCredential
146 completionHandler(disposition, credential)
149 @objc(URLSession:task:needNewBodyStream:)
151 _ session: URLSession,
152 task: URLSessionTask,
153 needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
155 var bodyStream: InputStream?
157 if let taskNeedNewBodyStream = taskNeedNewBodyStream {
158 bodyStream = taskNeedNewBodyStream(session, task)
161 completionHandler(bodyStream)
164 @objc(URLSession:task:didCompleteWithError:)
165 func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
166 if let taskDidCompleteWithError = taskDidCompleteWithError {
167 taskDidCompleteWithError(session, task, error)
169 if let error = error {
170 if self.error == nil { self.error = error }
173 let downloadDelegate = self as? DownloadTaskDelegate,
174 let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
176 downloadDelegate.resumeData = resumeData
180 queue.isSuspended = false
187 class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate {
191 var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
193 override var data: Data? {
194 if dataStream != nil {
201 var progress: Progress
202 var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
204 var dataStream: ((_ data: Data) -> Void)?
206 private var totalBytesReceived: Int64 = 0
207 private var mutableData: Data
209 private var expectedContentLength: Int64?
213 override init(task: URLSessionTask?) {
215 progress = Progress(totalUnitCount: 0)
217 super.init(task: task)
220 override func reset() {
223 progress = Progress(totalUnitCount: 0)
224 totalBytesReceived = 0
226 expectedContentLength = nil
229 // MARK: URLSessionDataDelegate
231 var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
232 var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
233 var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
234 var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
237 _ session: URLSession,
238 dataTask: URLSessionDataTask,
239 didReceive response: URLResponse,
240 completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
242 var disposition: URLSession.ResponseDisposition = .allow
244 expectedContentLength = response.expectedContentLength
246 if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
247 disposition = dataTaskDidReceiveResponse(session, dataTask, response)
250 completionHandler(disposition)
254 _ session: URLSession,
255 dataTask: URLSessionDataTask,
256 didBecome downloadTask: URLSessionDownloadTask)
258 dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
261 func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
262 if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
264 if let dataTaskDidReceiveData = dataTaskDidReceiveData {
265 dataTaskDidReceiveData(session, dataTask, data)
267 if let dataStream = dataStream {
270 mutableData.append(data)
273 let bytesReceived = Int64(data.count)
274 totalBytesReceived += bytesReceived
275 let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
277 progress.totalUnitCount = totalBytesExpected
278 progress.completedUnitCount = totalBytesReceived
280 if let progressHandler = progressHandler {
281 progressHandler.queue.async { progressHandler.closure(self.progress) }
287 _ session: URLSession,
288 dataTask: URLSessionDataTask,
289 willCacheResponse proposedResponse: CachedURLResponse,
290 completionHandler: @escaping (CachedURLResponse?) -> Void)
292 var cachedResponse: CachedURLResponse? = proposedResponse
294 if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
295 cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
298 completionHandler(cachedResponse)
304 class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
308 var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask }
310 var progress: Progress
311 var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
313 var resumeData: Data?
314 override var data: Data? { return resumeData }
316 var destination: DownloadRequest.DownloadFileDestination?
318 var temporaryURL: URL?
319 var destinationURL: URL?
321 var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
325 override init(task: URLSessionTask?) {
326 progress = Progress(totalUnitCount: 0)
327 super.init(task: task)
330 override func reset() {
333 progress = Progress(totalUnitCount: 0)
337 // MARK: URLSessionDownloadDelegate
339 var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)?
340 var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
341 var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
344 _ session: URLSession,
345 downloadTask: URLSessionDownloadTask,
346 didFinishDownloadingTo location: URL)
348 temporaryURL = location
351 let destination = destination,
352 let response = downloadTask.response as? HTTPURLResponse
355 let result = destination(location, response)
356 let destinationURL = result.destinationURL
357 let options = result.options
359 self.destinationURL = destinationURL
362 if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
363 try FileManager.default.removeItem(at: destinationURL)
366 if options.contains(.createIntermediateDirectories) {
367 let directory = destinationURL.deletingLastPathComponent()
368 try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
371 try FileManager.default.moveItem(at: location, to: destinationURL)
378 _ session: URLSession,
379 downloadTask: URLSessionDownloadTask,
380 didWriteData bytesWritten: Int64,
381 totalBytesWritten: Int64,
382 totalBytesExpectedToWrite: Int64)
384 if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
386 if let downloadTaskDidWriteData = downloadTaskDidWriteData {
387 downloadTaskDidWriteData(
392 totalBytesExpectedToWrite
395 progress.totalUnitCount = totalBytesExpectedToWrite
396 progress.completedUnitCount = totalBytesWritten
398 if let progressHandler = progressHandler {
399 progressHandler.queue.async { progressHandler.closure(self.progress) }
405 _ session: URLSession,
406 downloadTask: URLSessionDownloadTask,
407 didResumeAtOffset fileOffset: Int64,
408 expectedTotalBytes: Int64)
410 if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
411 downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
413 progress.totalUnitCount = expectedTotalBytes
414 progress.completedUnitCount = fileOffset
421 class UploadTaskDelegate: DataTaskDelegate {
425 var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask }
427 var uploadProgress: Progress
428 var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
432 override init(task: URLSessionTask?) {
433 uploadProgress = Progress(totalUnitCount: 0)
434 super.init(task: task)
437 override func reset() {
439 uploadProgress = Progress(totalUnitCount: 0)
442 // MARK: URLSessionTaskDelegate
444 var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
447 _ session: URLSession,
448 task: URLSessionTask,
449 didSendBodyData bytesSent: Int64,
450 totalBytesSent: Int64,
451 totalBytesExpectedToSend: Int64)
453 if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
455 if let taskDidSendBodyData = taskDidSendBodyData {
456 taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
458 uploadProgress.totalUnitCount = totalBytesExpectedToSend
459 uploadProgress.completedUnitCount = totalBytesSent
461 if let uploadProgressHandler = uploadProgressHandler {
462 uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) }