123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- //
- // TaskDelegate.swift
- //
- // Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Foundation
- /// The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
- /// executing all operations attached to the serial operation queue upon task completion.
- open class TaskDelegate: NSObject {
- // MARK: Properties
- /// The serial operation queue used to execute all operations after the task completes.
- public let queue: OperationQueue
- /// The data returned by the server.
- public var data: Data? { return nil }
- /// The error generated throughout the lifecyle of the task.
- public var error: Error?
- var task: URLSessionTask? {
- set {
- taskLock.lock(); defer { taskLock.unlock() }
- _task = newValue
- }
- get {
- taskLock.lock(); defer { taskLock.unlock() }
- return _task
- }
- }
- var initialResponseTime: CFAbsoluteTime?
- var credential: URLCredential?
- var metrics: AnyObject? // URLSessionTaskMetrics
- private var _task: URLSessionTask? {
- didSet { reset() }
- }
- private let taskLock = NSLock()
- // MARK: Lifecycle
- init(task: URLSessionTask?) {
- _task = task
- self.queue = {
- let operationQueue = OperationQueue()
- operationQueue.maxConcurrentOperationCount = 1
- operationQueue.isSuspended = true
- operationQueue.qualityOfService = .utility
- return operationQueue
- }()
- }
- func reset() {
- error = nil
- initialResponseTime = nil
- }
- // MARK: URLSessionTaskDelegate
- var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
- var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
- var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
- var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
- @objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
- func urlSession(
- _ session: URLSession,
- task: URLSessionTask,
- willPerformHTTPRedirection response: HTTPURLResponse,
- newRequest request: URLRequest,
- completionHandler: @escaping (URLRequest?) -> Void)
- {
- var redirectRequest: URLRequest? = request
- if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
- redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
- }
- completionHandler(redirectRequest)
- }
- @objc(URLSession:task:didReceiveChallenge:completionHandler:)
- func urlSession(
- _ session: URLSession,
- task: URLSessionTask,
- didReceive challenge: URLAuthenticationChallenge,
- completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
- {
- var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
- var credential: URLCredential?
- if let taskDidReceiveChallenge = taskDidReceiveChallenge {
- (disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
- } else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
- let host = challenge.protectionSpace.host
- if
- let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
- let serverTrust = challenge.protectionSpace.serverTrust
- {
- if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
- disposition = .useCredential
- credential = URLCredential(trust: serverTrust)
- } else {
- disposition = .cancelAuthenticationChallenge
- }
- }
- } else {
- if challenge.previousFailureCount > 0 {
- disposition = .rejectProtectionSpace
- } else {
- credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
- if credential != nil {
- disposition = .useCredential
- }
- }
- }
- completionHandler(disposition, credential)
- }
- @objc(URLSession:task:needNewBodyStream:)
- func urlSession(
- _ session: URLSession,
- task: URLSessionTask,
- needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
- {
- var bodyStream: InputStream?
- if let taskNeedNewBodyStream = taskNeedNewBodyStream {
- bodyStream = taskNeedNewBodyStream(session, task)
- }
- completionHandler(bodyStream)
- }
- @objc(URLSession:task:didCompleteWithError:)
- func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
- if let taskDidCompleteWithError = taskDidCompleteWithError {
- taskDidCompleteWithError(session, task, error)
- } else {
- if let error = error {
- if self.error == nil { self.error = error }
- if
- let downloadDelegate = self as? DownloadTaskDelegate,
- let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
- {
- downloadDelegate.resumeData = resumeData
- }
- }
- queue.isSuspended = false
- }
- }
- }
- // MARK: -
- class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate {
- // MARK: Properties
- var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
- override var data: Data? {
- if dataStream != nil {
- return nil
- } else {
- return mutableData
- }
- }
- var progress: Progress
- var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
- var dataStream: ((_ data: Data) -> Void)?
- private var totalBytesReceived: Int64 = 0
- private var mutableData: Data
- private var expectedContentLength: Int64?
- // MARK: Lifecycle
- override init(task: URLSessionTask?) {
- mutableData = Data()
- progress = Progress(totalUnitCount: 0)
- super.init(task: task)
- }
- override func reset() {
- super.reset()
- progress = Progress(totalUnitCount: 0)
- totalBytesReceived = 0
- mutableData = Data()
- expectedContentLength = nil
- }
- // MARK: URLSessionDataDelegate
- var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
- var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
- var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
- var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
- func urlSession(
- _ session: URLSession,
- dataTask: URLSessionDataTask,
- didReceive response: URLResponse,
- completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
- {
- var disposition: URLSession.ResponseDisposition = .allow
- expectedContentLength = response.expectedContentLength
- if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
- disposition = dataTaskDidReceiveResponse(session, dataTask, response)
- }
- completionHandler(disposition)
- }
- func urlSession(
- _ session: URLSession,
- dataTask: URLSessionDataTask,
- didBecome downloadTask: URLSessionDownloadTask)
- {
- dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
- }
- func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
- if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
- if let dataTaskDidReceiveData = dataTaskDidReceiveData {
- dataTaskDidReceiveData(session, dataTask, data)
- } else {
- if let dataStream = dataStream {
- dataStream(data)
- } else {
- mutableData.append(data)
- }
- let bytesReceived = Int64(data.count)
- totalBytesReceived += bytesReceived
- let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
- progress.totalUnitCount = totalBytesExpected
- progress.completedUnitCount = totalBytesReceived
- if let progressHandler = progressHandler {
- progressHandler.queue.async { progressHandler.closure(self.progress) }
- }
- }
- }
- func urlSession(
- _ session: URLSession,
- dataTask: URLSessionDataTask,
- willCacheResponse proposedResponse: CachedURLResponse,
- completionHandler: @escaping (CachedURLResponse?) -> Void)
- {
- var cachedResponse: CachedURLResponse? = proposedResponse
- if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
- cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
- }
- completionHandler(cachedResponse)
- }
- }
- // MARK: -
- class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
- // MARK: Properties
- var downloadTask: URLSessionDownloadTask { return task as! URLSessionDownloadTask }
- var progress: Progress
- var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
- var resumeData: Data?
- override var data: Data? { return resumeData }
- var destination: DownloadRequest.DownloadFileDestination?
- var temporaryURL: URL?
- var destinationURL: URL?
- var fileURL: URL? { return destination != nil ? destinationURL : temporaryURL }
- // MARK: Lifecycle
- override init(task: URLSessionTask?) {
- progress = Progress(totalUnitCount: 0)
- super.init(task: task)
- }
- override func reset() {
- super.reset()
- progress = Progress(totalUnitCount: 0)
- resumeData = nil
- }
- // MARK: URLSessionDownloadDelegate
- var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> URL)?
- var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
- var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
- func urlSession(
- _ session: URLSession,
- downloadTask: URLSessionDownloadTask,
- didFinishDownloadingTo location: URL)
- {
- temporaryURL = location
- guard
- let destination = destination,
- let response = downloadTask.response as? HTTPURLResponse
- else { return }
- let result = destination(location, response)
- let destinationURL = result.destinationURL
- let options = result.options
- self.destinationURL = destinationURL
- do {
- if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
- try FileManager.default.removeItem(at: destinationURL)
- }
- if options.contains(.createIntermediateDirectories) {
- let directory = destinationURL.deletingLastPathComponent()
- try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
- }
- try FileManager.default.moveItem(at: location, to: destinationURL)
- } catch {
- self.error = error
- }
- }
- func urlSession(
- _ session: URLSession,
- downloadTask: URLSessionDownloadTask,
- didWriteData bytesWritten: Int64,
- totalBytesWritten: Int64,
- totalBytesExpectedToWrite: Int64)
- {
- if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
- if let downloadTaskDidWriteData = downloadTaskDidWriteData {
- downloadTaskDidWriteData(
- session,
- downloadTask,
- bytesWritten,
- totalBytesWritten,
- totalBytesExpectedToWrite
- )
- } else {
- progress.totalUnitCount = totalBytesExpectedToWrite
- progress.completedUnitCount = totalBytesWritten
- if let progressHandler = progressHandler {
- progressHandler.queue.async { progressHandler.closure(self.progress) }
- }
- }
- }
- func urlSession(
- _ session: URLSession,
- downloadTask: URLSessionDownloadTask,
- didResumeAtOffset fileOffset: Int64,
- expectedTotalBytes: Int64)
- {
- if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
- downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
- } else {
- progress.totalUnitCount = expectedTotalBytes
- progress.completedUnitCount = fileOffset
- }
- }
- }
- // MARK: -
- class UploadTaskDelegate: DataTaskDelegate {
- // MARK: Properties
- var uploadTask: URLSessionUploadTask { return task as! URLSessionUploadTask }
- var uploadProgress: Progress
- var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
- // MARK: Lifecycle
- override init(task: URLSessionTask?) {
- uploadProgress = Progress(totalUnitCount: 0)
- super.init(task: task)
- }
- override func reset() {
- super.reset()
- uploadProgress = Progress(totalUnitCount: 0)
- }
- // MARK: URLSessionTaskDelegate
- var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
- func URLSession(
- _ session: URLSession,
- task: URLSessionTask,
- didSendBodyData bytesSent: Int64,
- totalBytesSent: Int64,
- totalBytesExpectedToSend: Int64)
- {
- if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
- if let taskDidSendBodyData = taskDidSendBodyData {
- taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
- } else {
- uploadProgress.totalUnitCount = totalBytesExpectedToSend
- uploadProgress.completedUnitCount = totalBytesSent
- if let uploadProgressHandler = uploadProgressHandler {
- uploadProgressHandler.queue.async { uploadProgressHandler.closure(self.uploadProgress) }
- }
- }
- }
- }
|