// // SessionDelegate.swift // // Copyright (c) 2014-2017 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? private 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) NotificationCenter.default.post( name: Notification.Name.Task.DidComplete, object: strongSelf, userInfo: [Notification.Key.Task: task] ) 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