726 lines
35 KiB
Swift
726 lines
35 KiB
Swift
//
|
||
// SessionDelegate.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
|
||
|
||
/// 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
|