// // SessionDelegate.swift // // Copyright (c) 2014-2016 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. public class SessionDelegate: NSObject { // MARK: URLSessionDelegate Overrides /// Overrides default behavior for NSURLSessionDelegate method `URLSession:didBecomeInvalidWithError:`. public var sessionDidBecomeInvalidWithError: ((URLSession, NSError?) -> Void)? /// Overrides default behavior for NSURLSessionDelegate method `URLSession:didReceiveChallenge:completionHandler:`. public var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? /// Overrides all behavior for NSURLSessionDelegate method `URLSession:didReceiveChallenge:completionHandler:` and requires the caller to call the `completionHandler`. public var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? /// Overrides default behavior for NSURLSessionDelegate method `URLSessionDidFinishEventsForBackgroundURLSession:`. public var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)? // MARK: URLSessionTaskDelegate Overrides /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:`. public var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)? /// Overrides all behavior for NSURLSessionTaskDelegate method `URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:` and /// requires the caller to call the `completionHandler`. public var taskWillPerformHTTPRedirectionWithCompletion: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest, (URLRequest?) -> Void) -> Void)? /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didReceiveChallenge:completionHandler:`. public var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))? /// Overrides all behavior for NSURLSessionTaskDelegate method `URLSession:task:didReceiveChallenge:completionHandler:` and /// requires the caller to call the `completionHandler`. public var taskDidReceiveChallengeWithCompletion: ((URLSession, URLSessionTask, URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)? /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:session:task:needNewBodyStream:`. public var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)? /// Overrides all behavior for NSURLSessionTaskDelegate method `URLSession:session:task:needNewBodyStream:` and /// requires the caller to call the `completionHandler`. public var taskNeedNewBodyStreamWithCompletion: ((URLSession, URLSessionTask, (InputStream?) -> Void) -> Void)? /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`. public var taskDidSendBodyData: ((URLSession, URLSessionTask, Int64, Int64, Int64) -> Void)? /// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didCompleteWithError:`. public var taskDidComplete: ((URLSession, URLSessionTask, NSError?) -> Void)? // MARK: URLSessionDataDelegate Overrides /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveResponse:completionHandler:`. public var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)? /// Overrides all behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveResponse:completionHandler:` and /// requires caller to call the `completionHandler`. public var dataTaskDidReceiveResponseWithCompletion: ((URLSession, URLSessionDataTask, URLResponse, (URLSession.ResponseDisposition) -> Void) -> Void)? /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didBecomeDownloadTask:`. public var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)? /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveData:`. public var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)? /// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:willCacheResponse:completionHandler:`. public var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)? /// Overrides all behavior for NSURLSessionDataDelegate method `URLSession:dataTask:willCacheResponse:completionHandler:` and /// requires caller to call the `completionHandler`. public var dataTaskWillCacheResponseWithCompletion: ((URLSession, URLSessionDataTask, CachedURLResponse, (CachedURLResponse?) -> Void) -> Void)? // MARK: NSURLSessionDownloadDelegate Overrides /// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didFinishDownloadingToURL:`. public var downloadTaskDidFinishDownloadingToURL: ((URLSession, URLSessionDownloadTask, URL) -> Void)? /// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:`. public var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)? /// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`. public var downloadTaskDidResumeAtOffset: ((URLSession, URLSessionDownloadTask, Int64, Int64) -> Void)? // MARK: URLSessionStreamDelegate Overrides #if !os(watchOS) /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:readClosedForStreamTask:`. public var streamTaskReadClosed: ((URLSession, URLSessionStreamTask) -> Void)? /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:writeClosedForStreamTask:`. public var streamTaskWriteClosed: ((URLSession, URLSessionStreamTask) -> Void)? /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:betterRouteDiscoveredForStreamTask:`. public var streamTaskBetterRouteDiscovered: ((URLSession, URLSessionStreamTask) -> Void)? /// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:streamTask:didBecomeInputStream:outputStream:`. public var streamTaskDidBecomeInputStream: ((URLSession, URLSessionStreamTask, InputStream, NSOutputStream) -> Void)? #endif // MARK: Properties private var subdelegates: [Int: TaskDelegate] = [:] private let subdelegateQueue = DispatchQueue(label: "Alamofire Sub Delegate Queue", attributes: .concurrent) /// Access the task delegate for the specified task in a thread-safe manner. public subscript(task: URLSessionTask) -> TaskDelegate? { get { var subdelegate: TaskDelegate? subdelegateQueue.sync { subdelegate = self.subdelegates[task.taskIdentifier] } return subdelegate } set { subdelegateQueue.async { self.subdelegates[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`. public override func responds(to selector: Selector) -> Bool { #if !os(OSX) if selector == #selector(URLSessionDelegate.urlSessionDidFinishEvents(forBackgroundURLSession:)) { return sessionDidFinishEventsForBackgroundURLSession != nil } #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 self.dynamicType.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. public 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. public func urlSession( _ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: ((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(OSX) /// Tells the delegate that all messages enqueued for a session have been delivered. /// /// - parameter session: The session that no longer has any outstanding requests. public 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. public func urlSession( _ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: (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. public func urlSession( _ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: (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.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. public func urlSession( _ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: (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.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. public 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] as? UploadTaskDelegate { delegate.URLSession( session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend ) } } /// 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. public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let taskDidComplete = taskDidComplete { taskDidComplete(session, task, error) } else if let delegate = self[task] { delegate.urlSession(session, task: task, didCompleteWithError: error) } NotificationCenter.default.post( name: Notification.Name.Task.DidComplete, object: self, userInfo: [Notification.Key.Task: task] ) self[task] = nil } } // 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. public func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (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. public func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) { if let dataTaskDidBecomeDownloadTask = dataTaskDidBecomeDownloadTask { dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask) } else { let downloadDelegate = DownloadTaskDelegate(task: downloadTask) self[downloadTask] = downloadDelegate } } /// 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. public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { if let dataTaskDidReceiveData = dataTaskDidReceiveData { dataTaskDidReceiveData(session, dataTask, data) } else if let delegate = self[dataTask] 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. public func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: (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] 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. public func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL { downloadTaskDidFinishDownloadingToURL(session, downloadTask, location) } else if let delegate = self[downloadTask] 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`. public 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] 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. public 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] as? DownloadTaskDelegate { delegate.urlSession( session, downloadTask: downloadTask, didResumeAtOffset: fileOffset, expectedTotalBytes: expectedTotalBytes ) } } } // MARK: - URLSessionStreamDelegate #if !os(watchOS) 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. public 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. public 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. public 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. public func urlSession( _ session: URLSession, streamTask: URLSessionStreamTask, didBecome inputStream: InputStream, outputStream: NSOutputStream) { streamTaskDidBecomeInputStream?(session, streamTask, inputStream, outputStream) } } #endif