2018-06-23 21:43:33 +08:00
|
|
|
|
//
|
|
|
|
|
// SessionDelegate.swift
|
|
|
|
|
//
|
2019-03-19 12:19:08 +08:00
|
|
|
|
// Copyright (c) 2014 Alamofire Software Foundation (http://alamofire.org/)
|
2018-06-23 21:43:33 +08:00
|
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
/// Responsible for handling all delegate callbacks for the underlying session.
|
|
|
|
|
open class SessionDelegate: NSObject {
|
|
|
|
|
|
|
|
|
|
// MARK: URLSessionDelegate Overrides
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didBecomeInvalidWithError:)`.
|
|
|
|
|
open var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`.
|
|
|
|
|
open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
|
|
|
|
|
|
|
|
|
|
/// Overrides all behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)` and requires the caller to call the `completionHandler`.
|
|
|
|
|
open var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDelegate method `urlSessionDidFinishEvents(forBackgroundURLSession:)`.
|
|
|
|
|
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
|
|
|
|
|
|
|
|
|
|
// MARK: URLSessionTaskDelegate Overrides
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`.
|
|
|
|
|
open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
|
|
|
|
|
|
|
|
|
|
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)` and
|
|
|
|
|
/// requires the caller to call the `completionHandler`.
|
|
|
|
|
open var taskWillPerformHTTPRedirectionWithCompletion: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)`.
|
|
|
|
|
open var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
|
|
|
|
|
|
|
|
|
|
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:didReceive:completionHandler:)` and
|
|
|
|
|
/// requires the caller to call the `completionHandler`.
|
|
|
|
|
open var taskDidReceiveChallengeWithCompletion: ((URLSession, URLSessionTask, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)`.
|
|
|
|
|
open var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
|
|
|
|
|
|
|
|
|
|
/// Overrides all behavior for URLSessionTaskDelegate method `urlSession(_:task:needNewBodyStream:)` and
|
|
|
|
|
/// requires the caller to call the `completionHandler`.
|
|
|
|
|
open var taskNeedNewBodyStreamWithCompletion: ((URLSession, URLSessionTask, @escaping (InputStream?) -> Void) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)`.
|
|
|
|
|
open var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionTaskDelegate method `urlSession(_:task:didCompleteWithError:)`.
|
|
|
|
|
open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)?
|
|
|
|
|
|
|
|
|
|
// MARK: URLSessionDataDelegate Overrides
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)`.
|
|
|
|
|
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
|
|
|
|
|
|
|
|
|
|
/// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:completionHandler:)` and
|
|
|
|
|
/// requires caller to call the `completionHandler`.
|
|
|
|
|
open var dataTaskDidReceiveResponseWithCompletion: ((URLSession, URLSessionDataTask, URLResponse, @escaping (URLSession.ResponseDisposition) -> Void) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didBecome:)`.
|
|
|
|
|
open var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:didReceive:)`.
|
|
|
|
|
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)`.
|
|
|
|
|
open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
|
|
|
|
|
|
|
|
|
|
/// Overrides all behavior for URLSessionDataDelegate method `urlSession(_:dataTask:willCacheResponse:completionHandler:)` and
|
|
|
|
|
/// requires caller to call the `completionHandler`.
|
|
|
|
|
open var dataTaskWillCacheResponseWithCompletion: ((URLSession, URLSessionDataTask, CachedURLResponse, @escaping (CachedURLResponse?) -> Void) -> Void)?
|
|
|
|
|
|
|
|
|
|
// MARK: URLSessionDownloadDelegate Overrides
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didFinishDownloadingTo:)`.
|
|
|
|
|
open var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)`.
|
|
|
|
|
open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionDownloadDelegate method `urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)`.
|
|
|
|
|
open var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)?
|
|
|
|
|
|
|
|
|
|
// MARK: URLSessionStreamDelegate Overrides
|
|
|
|
|
|
|
|
|
|
#if !os(watchOS)
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:readClosedFor:)`.
|
|
|
|
|
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
|
|
|
|
|
open var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)? {
|
|
|
|
|
get {
|
|
|
|
|
return _streamTaskReadClosed as? (URLSession, URLSessionStreamTask) -> Void
|
|
|
|
|
}
|
|
|
|
|
set {
|
|
|
|
|
_streamTaskReadClosed = newValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:writeClosedFor:)`.
|
|
|
|
|
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
|
|
|
|
|
open var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)? {
|
|
|
|
|
get {
|
|
|
|
|
return _streamTaskWriteClosed as? (URLSession, URLSessionStreamTask) -> Void
|
|
|
|
|
}
|
|
|
|
|
set {
|
|
|
|
|
_streamTaskWriteClosed = newValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:betterRouteDiscoveredFor:)`.
|
|
|
|
|
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
|
|
|
|
|
open var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)? {
|
|
|
|
|
get {
|
|
|
|
|
return _streamTaskBetterRouteDiscovered as? (URLSession, URLSessionStreamTask) -> Void
|
|
|
|
|
}
|
|
|
|
|
set {
|
|
|
|
|
_streamTaskBetterRouteDiscovered = newValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:streamTask:didBecome:outputStream:)`.
|
|
|
|
|
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
|
|
|
|
|
open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)? {
|
|
|
|
|
get {
|
|
|
|
|
return _streamTaskDidBecomeInputStream as? (URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void
|
|
|
|
|
}
|
|
|
|
|
set {
|
|
|
|
|
_streamTaskDidBecomeInputStream = newValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _streamTaskReadClosed: Any?
|
|
|
|
|
var _streamTaskWriteClosed: Any?
|
|
|
|
|
var _streamTaskBetterRouteDiscovered: Any?
|
|
|
|
|
var _streamTaskDidBecomeInputStream: Any?
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// MARK: Properties
|
|
|
|
|
|
|
|
|
|
var retrier: RequestRetrier?
|
|
|
|
|
weak var sessionManager: SessionManager?
|
|
|
|
|
|
|
|
|
|
var requests: [Int: Request] = [:]
|
|
|
|
|
private let lock = NSLock()
|
|
|
|
|
|
|
|
|
|
/// Access the task delegate for the specified task in a thread-safe manner.
|
|
|
|
|
open subscript(task: URLSessionTask) -> Request? {
|
|
|
|
|
get {
|
|
|
|
|
lock.lock() ; defer { lock.unlock() }
|
|
|
|
|
return requests[task.taskIdentifier]
|
|
|
|
|
}
|
|
|
|
|
set {
|
|
|
|
|
lock.lock() ; defer { lock.unlock() }
|
|
|
|
|
requests[task.taskIdentifier] = newValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Lifecycle
|
|
|
|
|
|
|
|
|
|
/// Initializes the `SessionDelegate` instance.
|
|
|
|
|
///
|
|
|
|
|
/// - returns: The new `SessionDelegate` instance.
|
|
|
|
|
public override init() {
|
|
|
|
|
super.init()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: NSObject Overrides
|
|
|
|
|
|
|
|
|
|
/// Returns a `Bool` indicating whether the `SessionDelegate` implements or inherits a method that can respond
|
|
|
|
|
/// to a specified message.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter selector: A selector that identifies a message.
|
|
|
|
|
///
|
|
|
|
|
/// - returns: `true` if the receiver implements or inherits a method that can respond to selector, otherwise `false`.
|
|
|
|
|
open override func responds(to selector: Selector) -> Bool {
|
|
|
|
|
#if !os(macOS)
|
|
|
|
|
if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) {
|
|
|
|
|
return sessionDidFinishEventsForBackgroundURLSession != nil
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if !os(watchOS)
|
|
|
|
|
if #available(iOS 9.0, macOS 10.11, tvOS 9.0, *) {
|
|
|
|
|
switch selector {
|
|
|
|
|
case #selector(URLSessionStreamDelegate.urlSession(_:readClosedFor:)):
|
|
|
|
|
return streamTaskReadClosed != nil
|
|
|
|
|
case #selector(URLSessionStreamDelegate.urlSession(_:writeClosedFor:)):
|
|
|
|
|
return streamTaskWriteClosed != nil
|
|
|
|
|
case #selector(URLSessionStreamDelegate.urlSession(_:betterRouteDiscoveredFor:)):
|
|
|
|
|
return streamTaskBetterRouteDiscovered != nil
|
|
|
|
|
case #selector(URLSessionStreamDelegate.urlSession(_:streamTask:didBecome:outputStream:)):
|
|
|
|
|
return streamTaskDidBecomeInputAndOutputStreams != nil
|
|
|
|
|
default:
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
switch selector {
|
|
|
|
|
case #selector(URLSessionDelegate.urlSession(_:didBecomeInvalidWithError:)):
|
|
|
|
|
return sessionDidBecomeInvalidWithError != nil
|
|
|
|
|
case #selector(URLSessionDelegate.urlSession(_:didReceive:completionHandler:)):
|
|
|
|
|
return (sessionDidReceiveChallenge != nil || sessionDidReceiveChallengeWithCompletion != nil)
|
|
|
|
|
case #selector(URLSessionTaskDelegate.urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)):
|
|
|
|
|
return (taskWillPerformHTTPRedirection != nil || taskWillPerformHTTPRedirectionWithCompletion != nil)
|
|
|
|
|
case #selector(URLSessionDataDelegate.urlSession(_:dataTask:didReceive:completionHandler:)):
|
|
|
|
|
return (dataTaskDidReceiveResponse != nil || dataTaskDidReceiveResponseWithCompletion != nil)
|
|
|
|
|
default:
|
|
|
|
|
return type(of: self).instancesRespond(to: selector)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - URLSessionDelegate
|
|
|
|
|
|
|
|
|
|
extension SessionDelegate: URLSessionDelegate {
|
|
|
|
|
/// Tells the delegate that the session has been invalidated.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session object that was invalidated.
|
|
|
|
|
/// - parameter error: The error that caused invalidation, or nil if the invalidation was explicit.
|
|
|
|
|
open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
|
|
|
|
|
sessionDidBecomeInvalidWithError?(session, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Requests credentials from the delegate in response to a session-level authentication request from the
|
|
|
|
|
/// remote server.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the task that requested authentication.
|
|
|
|
|
/// - parameter challenge: An object that contains the request for authentication.
|
|
|
|
|
/// - parameter completionHandler: A handler that your delegate method must call providing the disposition
|
|
|
|
|
/// and credential.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
didReceive challenge: URLAuthenticationChallenge,
|
|
|
|
|
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
|
|
|
|
{
|
|
|
|
|
guard sessionDidReceiveChallengeWithCompletion == nil else {
|
|
|
|
|
sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
|
|
|
|
|
var credential: URLCredential?
|
|
|
|
|
|
|
|
|
|
if let sessionDidReceiveChallenge = sessionDidReceiveChallenge {
|
|
|
|
|
(disposition, credential) = sessionDidReceiveChallenge(session, 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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
completionHandler(disposition, credential)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if !os(macOS)
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that all messages enqueued for a session have been delivered.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session that no longer has any outstanding requests.
|
|
|
|
|
open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
|
|
|
|
|
sessionDidFinishEventsForBackgroundURLSession?(session)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - URLSessionTaskDelegate
|
|
|
|
|
|
|
|
|
|
extension SessionDelegate: URLSessionTaskDelegate {
|
|
|
|
|
/// Tells the delegate that the remote server requested an HTTP redirect.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the task whose request resulted in a redirect.
|
|
|
|
|
/// - parameter task: The task whose request resulted in a redirect.
|
|
|
|
|
/// - parameter response: An object containing the server’s response to the original request.
|
|
|
|
|
/// - parameter request: A URL request object filled out with the new location.
|
|
|
|
|
/// - parameter completionHandler: A closure that your handler should call with either the value of the request
|
|
|
|
|
/// parameter, a modified URL request object, or NULL to refuse the redirect and
|
|
|
|
|
/// return the body of the redirect response.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
task: URLSessionTask,
|
|
|
|
|
willPerformHTTPRedirection response: HTTPURLResponse,
|
|
|
|
|
newRequest request: URLRequest,
|
|
|
|
|
completionHandler: @escaping (URLRequest?) -> Void)
|
|
|
|
|
{
|
|
|
|
|
guard taskWillPerformHTTPRedirectionWithCompletion == nil else {
|
|
|
|
|
taskWillPerformHTTPRedirectionWithCompletion?(session, task, response, request, completionHandler)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var redirectRequest: URLRequest? = request
|
|
|
|
|
|
|
|
|
|
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
|
|
|
|
|
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
completionHandler(redirectRequest)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Requests credentials from the delegate in response to an authentication request from the remote server.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the task whose request requires authentication.
|
|
|
|
|
/// - parameter task: The task whose request requires authentication.
|
|
|
|
|
/// - parameter challenge: An object that contains the request for authentication.
|
|
|
|
|
/// - parameter completionHandler: A handler that your delegate method must call providing the disposition
|
|
|
|
|
/// and credential.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
task: URLSessionTask,
|
|
|
|
|
didReceive challenge: URLAuthenticationChallenge,
|
|
|
|
|
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
|
|
|
|
|
{
|
|
|
|
|
guard taskDidReceiveChallengeWithCompletion == nil else {
|
|
|
|
|
taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
|
|
|
|
|
let result = taskDidReceiveChallenge(session, task, challenge)
|
|
|
|
|
completionHandler(result.0, result.1)
|
|
|
|
|
} else if let delegate = self[task]?.delegate {
|
|
|
|
|
delegate.urlSession(
|
|
|
|
|
session,
|
|
|
|
|
task: task,
|
|
|
|
|
didReceive: challenge,
|
|
|
|
|
completionHandler: completionHandler
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
urlSession(session, didReceive: challenge, completionHandler: completionHandler)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate when a task requires a new request body stream to send to the remote server.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the task that needs a new body stream.
|
|
|
|
|
/// - parameter task: The task that needs a new body stream.
|
|
|
|
|
/// - parameter completionHandler: A completion handler that your delegate method should call with the new body stream.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
task: URLSessionTask,
|
|
|
|
|
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
|
|
|
|
|
{
|
|
|
|
|
guard taskNeedNewBodyStreamWithCompletion == nil else {
|
|
|
|
|
taskNeedNewBodyStreamWithCompletion?(session, task, completionHandler)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
|
|
|
|
|
completionHandler(taskNeedNewBodyStream(session, task))
|
|
|
|
|
} else if let delegate = self[task]?.delegate {
|
|
|
|
|
delegate.urlSession(session, task: task, needNewBodyStream: completionHandler)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Periodically informs the delegate of the progress of sending body content to the server.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the data task.
|
|
|
|
|
/// - parameter task: The data task.
|
|
|
|
|
/// - parameter bytesSent: The number of bytes sent since the last time this delegate method was called.
|
|
|
|
|
/// - parameter totalBytesSent: The total number of bytes sent so far.
|
|
|
|
|
/// - parameter totalBytesExpectedToSend: The expected length of the body data.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
task: URLSessionTask,
|
|
|
|
|
didSendBodyData bytesSent: Int64,
|
|
|
|
|
totalBytesSent: Int64,
|
|
|
|
|
totalBytesExpectedToSend: Int64)
|
|
|
|
|
{
|
|
|
|
|
if let taskDidSendBodyData = taskDidSendBodyData {
|
|
|
|
|
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
|
|
|
|
|
} else if let delegate = self[task]?.delegate as? UploadTaskDelegate {
|
|
|
|
|
delegate.URLSession(
|
|
|
|
|
session,
|
|
|
|
|
task: task,
|
|
|
|
|
didSendBodyData: bytesSent,
|
|
|
|
|
totalBytesSent: totalBytesSent,
|
|
|
|
|
totalBytesExpectedToSend: totalBytesExpectedToSend
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if !os(watchOS)
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the session finished collecting metrics for the task.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session collecting the metrics.
|
|
|
|
|
/// - parameter task: The task whose metrics have been collected.
|
|
|
|
|
/// - parameter metrics: The collected metrics.
|
|
|
|
|
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
|
|
|
|
|
@objc(URLSession:task:didFinishCollectingMetrics:)
|
|
|
|
|
open func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
|
|
|
|
|
self[task]?.delegate.metrics = metrics
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the task finished transferring data.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the task whose request finished transferring data.
|
|
|
|
|
/// - parameter task: The task whose request finished transferring data.
|
|
|
|
|
/// - parameter error: If an error occurred, an error object indicating how the transfer failed, otherwise nil.
|
|
|
|
|
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
|
|
|
/// Executed after it is determined that the request is not going to be retried
|
|
|
|
|
let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
|
|
|
|
|
guard let strongSelf = self else { return }
|
|
|
|
|
|
|
|
|
|
strongSelf.taskDidComplete?(session, task, error)
|
|
|
|
|
|
|
|
|
|
strongSelf[task]?.delegate.urlSession(session, task: task, didCompleteWithError: error)
|
|
|
|
|
|
|
|
|
|
var userInfo: [String: Any] = [Notification.Key.Task: task]
|
|
|
|
|
|
|
|
|
|
if let data = (strongSelf[task]?.delegate as? DataTaskDelegate)?.data {
|
|
|
|
|
userInfo[Notification.Key.ResponseData] = data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NotificationCenter.default.post(
|
|
|
|
|
name: Notification.Name.Task.DidComplete,
|
|
|
|
|
object: strongSelf,
|
|
|
|
|
userInfo: userInfo
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
strongSelf[task] = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard let request = self[task], let sessionManager = sessionManager else {
|
|
|
|
|
completeTask(session, task, error)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run all validations on the request before checking if an error occurred
|
|
|
|
|
request.validations.forEach { $0() }
|
|
|
|
|
|
|
|
|
|
// Determine whether an error has occurred
|
|
|
|
|
var error: Error? = error
|
|
|
|
|
|
|
|
|
|
if request.delegate.error != nil {
|
|
|
|
|
error = request.delegate.error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// If an error occurred and the retrier is set, asynchronously ask the retrier if the request
|
|
|
|
|
/// should be retried. Otherwise, complete the task by notifying the task delegate.
|
|
|
|
|
if let retrier = retrier, let error = error {
|
|
|
|
|
retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
|
|
|
|
|
guard shouldRetry else { completeTask(session, task, error) ; return }
|
|
|
|
|
|
|
|
|
|
DispatchQueue.utility.after(timeDelay) { [weak self] in
|
|
|
|
|
guard let strongSelf = self else { return }
|
|
|
|
|
|
|
|
|
|
let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false
|
|
|
|
|
|
|
|
|
|
if retrySucceeded, let task = request.task {
|
|
|
|
|
strongSelf[task] = request
|
|
|
|
|
return
|
|
|
|
|
} else {
|
|
|
|
|
completeTask(session, task, error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
completeTask(session, task, error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - URLSessionDataDelegate
|
|
|
|
|
|
|
|
|
|
extension SessionDelegate: URLSessionDataDelegate {
|
|
|
|
|
/// Tells the delegate that the data task received the initial reply (headers) from the server.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the data task that received an initial reply.
|
|
|
|
|
/// - parameter dataTask: The data task that received an initial reply.
|
|
|
|
|
/// - parameter response: A URL response object populated with headers.
|
|
|
|
|
/// - parameter completionHandler: A completion handler that your code calls to continue the transfer, passing a
|
|
|
|
|
/// constant to indicate whether the transfer should continue as a data task or
|
|
|
|
|
/// should become a download task.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
dataTask: URLSessionDataTask,
|
|
|
|
|
didReceive response: URLResponse,
|
|
|
|
|
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
|
|
|
|
|
{
|
|
|
|
|
guard dataTaskDidReceiveResponseWithCompletion == nil else {
|
|
|
|
|
dataTaskDidReceiveResponseWithCompletion?(session, dataTask, response, completionHandler)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var disposition: URLSession.ResponseDisposition = .allow
|
|
|
|
|
|
|
|
|
|
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
|
|
|
|
|
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
completionHandler(disposition)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the data task was changed to a download task.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the task that was replaced by a download task.
|
|
|
|
|
/// - parameter dataTask: The data task that was replaced by a download task.
|
|
|
|
|
/// - parameter downloadTask: The new download task that replaced the data task.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
dataTask: URLSessionDataTask,
|
|
|
|
|
didBecome downloadTask: URLSessionDownloadTask)
|
|
|
|
|
{
|
|
|
|
|
if let dataTaskDidBecomeDownloadTask = dataTaskDidBecomeDownloadTask {
|
|
|
|
|
dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask)
|
|
|
|
|
} else {
|
|
|
|
|
self[downloadTask]?.delegate = DownloadTaskDelegate(task: downloadTask)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the data task has received some of the expected data.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the data task that provided data.
|
|
|
|
|
/// - parameter dataTask: The data task that provided data.
|
|
|
|
|
/// - parameter data: A data object containing the transferred data.
|
|
|
|
|
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
|
|
|
|
|
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
|
|
|
|
|
dataTaskDidReceiveData(session, dataTask, data)
|
|
|
|
|
} else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {
|
|
|
|
|
delegate.urlSession(session, dataTask: dataTask, didReceive: data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Asks the delegate whether the data (or upload) task should store the response in the cache.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the data (or upload) task.
|
|
|
|
|
/// - parameter dataTask: The data (or upload) task.
|
|
|
|
|
/// - parameter proposedResponse: The default caching behavior. This behavior is determined based on the current
|
|
|
|
|
/// caching policy and the values of certain received headers, such as the Pragma
|
|
|
|
|
/// and Cache-Control headers.
|
|
|
|
|
/// - parameter completionHandler: A block that your handler must call, providing either the original proposed
|
|
|
|
|
/// response, a modified version of that response, or NULL to prevent caching the
|
|
|
|
|
/// response. If your delegate implements this method, it must call this completion
|
|
|
|
|
/// handler; otherwise, your app leaks memory.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
dataTask: URLSessionDataTask,
|
|
|
|
|
willCacheResponse proposedResponse: CachedURLResponse,
|
|
|
|
|
completionHandler: @escaping (CachedURLResponse?) -> Void)
|
|
|
|
|
{
|
|
|
|
|
guard dataTaskWillCacheResponseWithCompletion == nil else {
|
|
|
|
|
dataTaskWillCacheResponseWithCompletion?(session, dataTask, proposedResponse, completionHandler)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
|
|
|
|
|
completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse))
|
|
|
|
|
} else if let delegate = self[dataTask]?.delegate as? DataTaskDelegate {
|
|
|
|
|
delegate.urlSession(
|
|
|
|
|
session,
|
|
|
|
|
dataTask: dataTask,
|
|
|
|
|
willCacheResponse: proposedResponse,
|
|
|
|
|
completionHandler: completionHandler
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
completionHandler(proposedResponse)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - URLSessionDownloadDelegate
|
|
|
|
|
|
|
|
|
|
extension SessionDelegate: URLSessionDownloadDelegate {
|
|
|
|
|
/// Tells the delegate that a download task has finished downloading.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the download task that finished.
|
|
|
|
|
/// - parameter downloadTask: The download task that finished.
|
|
|
|
|
/// - parameter location: A file URL for the temporary file. Because the file is temporary, you must either
|
|
|
|
|
/// open the file for reading or move it to a permanent location in your app’s sandbox
|
|
|
|
|
/// container directory before returning from this delegate method.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
downloadTask: URLSessionDownloadTask,
|
|
|
|
|
didFinishDownloadingTo location: URL)
|
|
|
|
|
{
|
|
|
|
|
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
|
|
|
|
|
downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
|
|
|
|
|
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
|
|
|
|
|
delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Periodically informs the delegate about the download’s progress.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the download task.
|
|
|
|
|
/// - parameter downloadTask: The download task.
|
|
|
|
|
/// - parameter bytesWritten: The number of bytes transferred since the last time this delegate
|
|
|
|
|
/// method was called.
|
|
|
|
|
/// - parameter totalBytesWritten: The total number of bytes transferred so far.
|
|
|
|
|
/// - parameter totalBytesExpectedToWrite: The expected length of the file, as provided by the Content-Length
|
|
|
|
|
/// header. If this header was not provided, the value is
|
|
|
|
|
/// `NSURLSessionTransferSizeUnknown`.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
downloadTask: URLSessionDownloadTask,
|
|
|
|
|
didWriteData bytesWritten: Int64,
|
|
|
|
|
totalBytesWritten: Int64,
|
|
|
|
|
totalBytesExpectedToWrite: Int64)
|
|
|
|
|
{
|
|
|
|
|
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
|
|
|
|
|
downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
|
|
|
|
|
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
|
|
|
|
|
delegate.urlSession(
|
|
|
|
|
session,
|
|
|
|
|
downloadTask: downloadTask,
|
|
|
|
|
didWriteData: bytesWritten,
|
|
|
|
|
totalBytesWritten: totalBytesWritten,
|
|
|
|
|
totalBytesExpectedToWrite: totalBytesExpectedToWrite
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the download task has resumed downloading.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session containing the download task that finished.
|
|
|
|
|
/// - parameter downloadTask: The download task that resumed. See explanation in the discussion.
|
|
|
|
|
/// - parameter fileOffset: If the file's cache policy or last modified date prevents reuse of the
|
|
|
|
|
/// existing content, then this value is zero. Otherwise, this value is an
|
|
|
|
|
/// integer representing the number of bytes on disk that do not need to be
|
|
|
|
|
/// retrieved again.
|
|
|
|
|
/// - parameter expectedTotalBytes: The expected length of the file, as provided by the Content-Length header.
|
|
|
|
|
/// If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
downloadTask: URLSessionDownloadTask,
|
|
|
|
|
didResumeAtOffset fileOffset: Int64,
|
|
|
|
|
expectedTotalBytes: Int64)
|
|
|
|
|
{
|
|
|
|
|
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
|
|
|
|
|
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
|
|
|
|
|
} else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
|
|
|
|
|
delegate.urlSession(
|
|
|
|
|
session,
|
|
|
|
|
downloadTask: downloadTask,
|
|
|
|
|
didResumeAtOffset: fileOffset,
|
|
|
|
|
expectedTotalBytes: expectedTotalBytes
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - URLSessionStreamDelegate
|
|
|
|
|
|
|
|
|
|
#if !os(watchOS)
|
|
|
|
|
|
|
|
|
|
@available(iOS 9.0, macOS 10.11, tvOS 9.0, *)
|
|
|
|
|
extension SessionDelegate: URLSessionStreamDelegate {
|
|
|
|
|
/// Tells the delegate that the read side of the connection has been closed.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session.
|
|
|
|
|
/// - parameter streamTask: The stream task.
|
|
|
|
|
open func urlSession(_ session: URLSession, readClosedFor streamTask: URLSessionStreamTask) {
|
|
|
|
|
streamTaskReadClosed?(session, streamTask)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the write side of the connection has been closed.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session.
|
|
|
|
|
/// - parameter streamTask: The stream task.
|
|
|
|
|
open func urlSession(_ session: URLSession, writeClosedFor streamTask: URLSessionStreamTask) {
|
|
|
|
|
streamTaskWriteClosed?(session, streamTask)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the system has determined that a better route to the host is available.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session.
|
|
|
|
|
/// - parameter streamTask: The stream task.
|
|
|
|
|
open func urlSession(_ session: URLSession, betterRouteDiscoveredFor streamTask: URLSessionStreamTask) {
|
|
|
|
|
streamTaskBetterRouteDiscovered?(session, streamTask)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tells the delegate that the stream task has been completed and provides the unopened stream objects.
|
|
|
|
|
///
|
|
|
|
|
/// - parameter session: The session.
|
|
|
|
|
/// - parameter streamTask: The stream task.
|
|
|
|
|
/// - parameter inputStream: The new input stream.
|
|
|
|
|
/// - parameter outputStream: The new output stream.
|
|
|
|
|
open func urlSession(
|
|
|
|
|
_ session: URLSession,
|
|
|
|
|
streamTask: URLSessionStreamTask,
|
|
|
|
|
didBecome inputStream: InputStream,
|
|
|
|
|
outputStream: OutputStream)
|
|
|
|
|
{
|
|
|
|
|
streamTaskDidBecomeInputAndOutputStreams?(session, streamTask, inputStream, outputStream)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|