467 lines
15 KiB
Swift
467 lines
15 KiB
Swift
//
|
|
// TaskDelegate.swift
|
|
//
|
|
// Copyright (c) 2014-2018 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.
|
|
open 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) }
|
|
}
|
|
}
|
|
}
|
|
}
|