| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 | ////  SessionManager.swift////  Copyright (c) 2014 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 creating and managing `Request` objects, as well as their underlying `NSURLSession`.open class SessionManager {    // MARK: - Helper Types    /// Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as    /// associated values.    ///    /// - Success: Represents a successful `MultipartFormData` encoding and contains the new `UploadRequest` along with    ///            streaming information.    /// - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding    ///            error.    public enum MultipartFormDataEncodingResult {        case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)        case failure(Error)    }    // MARK: - Properties    /// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use    /// directly for any ad hoc requests.    public static let `default`: SessionManager = {        let configuration = URLSessionConfiguration.default        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders        return SessionManager(configuration: configuration)    }()    /// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.    public static let defaultHTTPHeaders: HTTPHeaders = {        // Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3        let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"        // Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5        let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in            let quality = 1.0 - (Double(index) * 0.1)            return "\(languageCode);q=\(quality)"        }.joined(separator: ", ")        // User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3        // Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0`        let userAgent: String = {            if let info = Bundle.main.infoDictionary {                let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"                let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"                let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"                let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"                let osNameVersion: String = {                    let version = ProcessInfo.processInfo.operatingSystemVersion                    let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"                    let osName: String = {                        #if os(iOS)                            return "iOS"                        #elseif os(watchOS)                            return "watchOS"                        #elseif os(tvOS)                            return "tvOS"                        #elseif os(macOS)                            return "OS X"                        #elseif os(Linux)                            return "Linux"                        #else                            return "Unknown"                        #endif                    }()                    return "\(osName) \(versionString)"                }()                let alamofireVersion: String = {                    guard                        let afInfo = Bundle(for: SessionManager.self).infoDictionary,                        let build = afInfo["CFBundleShortVersionString"]                    else { return "Unknown" }                    return "Alamofire/\(build)"                }()                return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"            }            return "Alamofire"        }()        return [            "Accept-Encoding": acceptEncoding,            "Accept-Language": acceptLanguage,            "User-Agent": userAgent        ]    }()    /// Default memory threshold used when encoding `MultipartFormData` in bytes.    public static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000    /// The underlying session.    public let session: URLSession    /// The session delegate handling all the task and session delegate callbacks.    public let delegate: SessionDelegate    /// Whether to start requests immediately after being constructed. `true` by default.    open var startRequestsImmediately: Bool = true    /// The request adapter called each time a new request is created.    open var adapter: RequestAdapter?    /// The request retrier called each time a request encounters an error to determine whether to retry the request.    open var retrier: RequestRetrier? {        get { return delegate.retrier }        set { delegate.retrier = newValue }    }    /// The background completion handler closure provided by the UIApplicationDelegate    /// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background    /// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation    /// will automatically call the handler.    ///    /// If you need to handle your own events before the handler is called, then you need to override the    /// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.    ///    /// `nil` by default.    open var backgroundCompletionHandler: (() -> Void)?    let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)    // MARK: - Lifecycle    /// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.    ///    /// - parameter configuration:            The configuration used to construct the managed session.    ///                                       `URLSessionConfiguration.default` by default.    /// - parameter delegate:                 The delegate used when initializing the session. `SessionDelegate()` by    ///                                       default.    /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust    ///                                       challenges. `nil` by default.    ///    /// - returns: The new `SessionManager` instance.    public init(        configuration: URLSessionConfiguration = URLSessionConfiguration.default,        delegate: SessionDelegate = SessionDelegate(),        serverTrustPolicyManager: ServerTrustPolicyManager? = nil)    {        self.delegate = delegate        self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)        commonInit(serverTrustPolicyManager: serverTrustPolicyManager)    }    /// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.    ///    /// - parameter session:                  The URL session.    /// - parameter delegate:                 The delegate of the URL session. Must equal the URL session's delegate.    /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust    ///                                       challenges. `nil` by default.    ///    /// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.    public init?(        session: URLSession,        delegate: SessionDelegate,        serverTrustPolicyManager: ServerTrustPolicyManager? = nil)    {        guard delegate === session.delegate else { return nil }        self.delegate = delegate        self.session = session        commonInit(serverTrustPolicyManager: serverTrustPolicyManager)    }    private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {        session.serverTrustPolicyManager = serverTrustPolicyManager        delegate.sessionManager = self        delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in            guard let strongSelf = self else { return }            DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }        }    }    deinit {        session.invalidateAndCancel()    }    // MARK: - Data Request    /// Creates a `DataRequest` to retrieve the contents of the specified `url`, `method`, `parameters`, `encoding`    /// and `headers`.    ///    /// - parameter url:        The URL.    /// - parameter method:     The HTTP method. `.get` by default.    /// - parameter parameters: The parameters. `nil` by default.    /// - parameter encoding:   The parameter encoding. `URLEncoding.default` by default.    /// - parameter headers:    The HTTP headers. `nil` by default.    ///    /// - returns: The created `DataRequest`.    @discardableResult    open func request(        _ url: URLConvertible,        method: HTTPMethod = .get,        parameters: Parameters? = nil,        encoding: ParameterEncoding = URLEncoding.default,        headers: HTTPHeaders? = nil)        -> DataRequest    {        var originalRequest: URLRequest?        do {            originalRequest = try URLRequest(url: url, method: method, headers: headers)            let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)            return request(encodedURLRequest)        } catch {            return request(originalRequest, failedWith: error)        }    }    /// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter urlRequest: The URL request.    ///    /// - returns: The created `DataRequest`.    @discardableResult    open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {        var originalRequest: URLRequest?        do {            originalRequest = try urlRequest.asURLRequest()            let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)            let request = DataRequest(session: session, requestTask: .data(originalTask, task))            delegate[task] = request            if startRequestsImmediately { request.resume() }            return request        } catch {            return request(originalRequest, failedWith: error)        }    }    // MARK: Private - Request Implementation    private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest {        var requestTask: Request.RequestTask = .data(nil, nil)        if let urlRequest = urlRequest {            let originalTask = DataRequest.Requestable(urlRequest: urlRequest)            requestTask = .data(originalTask, nil)        }        let underlyingError = error.underlyingAdaptError ?? error        let request = DataRequest(session: session, requestTask: requestTask, error: underlyingError)        if let retrier = retrier, error is AdaptError {            allowRetrier(retrier, toRetry: request, with: underlyingError)        } else {            if startRequestsImmediately { request.resume() }        }        return request    }    // MARK: - Download Request    // MARK: URL Request    /// Creates a `DownloadRequest` to retrieve the contents the specified `url`, `method`, `parameters`, `encoding`,    /// `headers` and save them to the `destination`.    ///    /// If `destination` is not specified, the contents will remain in the temporary location determined by the    /// underlying URL session.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter url:         The URL.    /// - parameter method:      The HTTP method. `.get` by default.    /// - parameter parameters:  The parameters. `nil` by default.    /// - parameter encoding:    The parameter encoding. `URLEncoding.default` by default.    /// - parameter headers:     The HTTP headers. `nil` by default.    /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.    ///    /// - returns: The created `DownloadRequest`.    @discardableResult    open func download(        _ url: URLConvertible,        method: HTTPMethod = .get,        parameters: Parameters? = nil,        encoding: ParameterEncoding = URLEncoding.default,        headers: HTTPHeaders? = nil,        to destination: DownloadRequest.DownloadFileDestination? = nil)        -> DownloadRequest    {        do {            let urlRequest = try URLRequest(url: url, method: method, headers: headers)            let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)            return download(encodedURLRequest, to: destination)        } catch {            return download(nil, to: destination, failedWith: error)        }    }    /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlRequest` and save    /// them to the `destination`.    ///    /// If `destination` is not specified, the contents will remain in the temporary location determined by the    /// underlying URL session.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter urlRequest:  The URL request    /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.    ///    /// - returns: The created `DownloadRequest`.    @discardableResult    open func download(        _ urlRequest: URLRequestConvertible,        to destination: DownloadRequest.DownloadFileDestination? = nil)        -> DownloadRequest    {        do {            let urlRequest = try urlRequest.asURLRequest()            return download(.request(urlRequest), to: destination)        } catch {            return download(nil, to: destination, failedWith: error)        }    }    // MARK: Resume Data    /// Creates a `DownloadRequest` from the `resumeData` produced from a previous request cancellation to retrieve    /// the contents of the original request and save them to the `destination`.    ///    /// If `destination` is not specified, the contents will remain in the temporary location determined by the    /// underlying URL session.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// On the latest release of all the Apple platforms (iOS 10, macOS 10.12, tvOS 10, watchOS 3), `resumeData` is broken    /// on background URL session configurations. There's an underlying bug in the `resumeData` generation logic where the    /// data is written incorrectly and will always fail to resume the download. For more information about the bug and    /// possible workarounds, please refer to the following Stack Overflow post:    ///    ///    - http://stackoverflow.com/a/39347461/1342462    ///    /// - parameter resumeData:  The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`    ///                          when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for    ///                          additional information.    /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.    ///    /// - returns: The created `DownloadRequest`.    @discardableResult    open func download(        resumingWith resumeData: Data,        to destination: DownloadRequest.DownloadFileDestination? = nil)        -> DownloadRequest    {        return download(.resumeData(resumeData), to: destination)    }    // MARK: Private - Download Implementation    private func download(        _ downloadable: DownloadRequest.Downloadable,        to destination: DownloadRequest.DownloadFileDestination?)        -> DownloadRequest    {        do {            let task = try downloadable.task(session: session, adapter: adapter, queue: queue)            let download = DownloadRequest(session: session, requestTask: .download(downloadable, task))            download.downloadDelegate.destination = destination            delegate[task] = download            if startRequestsImmediately { download.resume() }            return download        } catch {            return download(downloadable, to: destination, failedWith: error)        }    }    private func download(        _ downloadable: DownloadRequest.Downloadable?,        to destination: DownloadRequest.DownloadFileDestination?,        failedWith error: Error)        -> DownloadRequest    {        var downloadTask: Request.RequestTask = .download(nil, nil)        if let downloadable = downloadable {            downloadTask = .download(downloadable, nil)        }        let underlyingError = error.underlyingAdaptError ?? error        let download = DownloadRequest(session: session, requestTask: downloadTask, error: underlyingError)        download.downloadDelegate.destination = destination        if let retrier = retrier, error is AdaptError {            allowRetrier(retrier, toRetry: download, with: underlyingError)        } else {            if startRequestsImmediately { download.resume() }        }        return download    }    // MARK: - Upload Request    // MARK: File    /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `file`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter file:    The file to upload.    /// - parameter url:     The URL.    /// - parameter method:  The HTTP method. `.post` by default.    /// - parameter headers: The HTTP headers. `nil` by default.    ///    /// - returns: The created `UploadRequest`.    @discardableResult    open func upload(        _ fileURL: URL,        to url: URLConvertible,        method: HTTPMethod = .post,        headers: HTTPHeaders? = nil)        -> UploadRequest    {        do {            let urlRequest = try URLRequest(url: url, method: method, headers: headers)            return upload(fileURL, with: urlRequest)        } catch {            return upload(nil, failedWith: error)        }    }    /// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter file:       The file to upload.    /// - parameter urlRequest: The URL request.    ///    /// - returns: The created `UploadRequest`.    @discardableResult    open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {        do {            let urlRequest = try urlRequest.asURLRequest()            return upload(.file(fileURL, urlRequest))        } catch {            return upload(nil, failedWith: error)        }    }    // MARK: Data    /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `data`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter data:    The data to upload.    /// - parameter url:     The URL.    /// - parameter method:  The HTTP method. `.post` by default.    /// - parameter headers: The HTTP headers. `nil` by default.    ///    /// - returns: The created `UploadRequest`.    @discardableResult    open func upload(        _ data: Data,        to url: URLConvertible,        method: HTTPMethod = .post,        headers: HTTPHeaders? = nil)        -> UploadRequest    {        do {            let urlRequest = try URLRequest(url: url, method: method, headers: headers)            return upload(data, with: urlRequest)        } catch {            return upload(nil, failedWith: error)        }    }    /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter data:       The data to upload.    /// - parameter urlRequest: The URL request.    ///    /// - returns: The created `UploadRequest`.    @discardableResult    open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {        do {            let urlRequest = try urlRequest.asURLRequest()            return upload(.data(data, urlRequest))        } catch {            return upload(nil, failedWith: error)        }    }    // MARK: InputStream    /// Creates an `UploadRequest` from the specified `url`, `method` and `headers` for uploading the `stream`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter stream:  The stream to upload.    /// - parameter url:     The URL.    /// - parameter method:  The HTTP method. `.post` by default.    /// - parameter headers: The HTTP headers. `nil` by default.    ///    /// - returns: The created `UploadRequest`.    @discardableResult    open func upload(        _ stream: InputStream,        to url: URLConvertible,        method: HTTPMethod = .post,        headers: HTTPHeaders? = nil)        -> UploadRequest    {        do {            let urlRequest = try URLRequest(url: url, method: method, headers: headers)            return upload(stream, with: urlRequest)        } catch {            return upload(nil, failedWith: error)        }    }    /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter stream:     The stream to upload.    /// - parameter urlRequest: The URL request.    ///    /// - returns: The created `UploadRequest`.    @discardableResult    open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {        do {            let urlRequest = try urlRequest.asURLRequest()            return upload(.stream(stream, urlRequest))        } catch {            return upload(nil, failedWith: error)        }    }    // MARK: MultipartFormData    /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new    /// `UploadRequest` using the `url`, `method` and `headers`.    ///    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative    /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most    /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to    /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory    /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be    /// used for larger payloads such as video content.    ///    /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory    /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,    /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk    /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding    /// technique was used.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter multipartFormData:       The closure used to append body parts to the `MultipartFormData`.    /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.    ///                                      `multipartFormDataEncodingMemoryThreshold` by default.    /// - parameter url:                     The URL.    /// - parameter method:                  The HTTP method. `.post` by default.    /// - parameter headers:                 The HTTP headers. `nil` by default.    /// - parameter encodingCompletion:      The closure called when the `MultipartFormData` encoding is complete.    open func upload(        multipartFormData: @escaping (MultipartFormData) -> Void,        usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,        to url: URLConvertible,        method: HTTPMethod = .post,        headers: HTTPHeaders? = nil,        queue: DispatchQueue? = nil,        encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)    {        do {            let urlRequest = try URLRequest(url: url, method: method, headers: headers)            return upload(                multipartFormData: multipartFormData,                usingThreshold: encodingMemoryThreshold,                with: urlRequest,                queue: queue,                encodingCompletion: encodingCompletion            )        } catch {            (queue ?? DispatchQueue.main).async { encodingCompletion?(.failure(error)) }        }    }    /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new    /// `UploadRequest` using the `urlRequest`.    ///    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative    /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most    /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to    /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory    /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be    /// used for larger payloads such as video content.    ///    /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory    /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,    /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk    /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding    /// technique was used.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter multipartFormData:       The closure used to append body parts to the `MultipartFormData`.    /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.    ///                                      `multipartFormDataEncodingMemoryThreshold` by default.    /// - parameter urlRequest:              The URL request.    /// - parameter encodingCompletion:      The closure called when the `MultipartFormData` encoding is complete.    open func upload(        multipartFormData: @escaping (MultipartFormData) -> Void,        usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,        with urlRequest: URLRequestConvertible,        queue: DispatchQueue? = nil,        encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)    {        DispatchQueue.global(qos: .utility).async {            let formData = MultipartFormData()            multipartFormData(formData)            var tempFileURL: URL?            do {                var urlRequestWithContentType = try urlRequest.asURLRequest()                urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")                let isBackgroundSession = self.session.configuration.identifier != nil                if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {                    let data = try formData.encode()                    let encodingResult = MultipartFormDataEncodingResult.success(                        request: self.upload(data, with: urlRequestWithContentType),                        streamingFromDisk: false,                        streamFileURL: nil                    )                    (queue ?? DispatchQueue.main).async { encodingCompletion?(encodingResult) }                } else {                    let fileManager = FileManager.default                    let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())                    let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")                    let fileName = UUID().uuidString                    let fileURL = directoryURL.appendingPathComponent(fileName)                    tempFileURL = fileURL                    var directoryError: Error?                    // Create directory inside serial queue to ensure two threads don't do this in parallel                    self.queue.sync {                        do {                            try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)                        } catch {                            directoryError = error                        }                    }                    if let directoryError = directoryError { throw directoryError }                    try formData.writeEncodedData(to: fileURL)                    let upload = self.upload(fileURL, with: urlRequestWithContentType)                    // Cleanup the temp file once the upload is complete                    upload.delegate.queue.addOperation {                        do {                            try FileManager.default.removeItem(at: fileURL)                        } catch {                            // No-op                        }                    }                    (queue ?? DispatchQueue.main).async {                        let encodingResult = MultipartFormDataEncodingResult.success(                            request: upload,                            streamingFromDisk: true,                            streamFileURL: fileURL                        )                        encodingCompletion?(encodingResult)                    }                }            } catch {                // Cleanup the temp file in the event that the multipart form data encoding failed                if let tempFileURL = tempFileURL {                    do {                        try FileManager.default.removeItem(at: tempFileURL)                    } catch {                        // No-op                    }                }                (queue ?? DispatchQueue.main).async { encodingCompletion?(.failure(error)) }            }        }    }    // MARK: Private - Upload Implementation    private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {        do {            let task = try uploadable.task(session: session, adapter: adapter, queue: queue)            let upload = UploadRequest(session: session, requestTask: .upload(uploadable, task))            if case let .stream(inputStream, _) = uploadable {                upload.delegate.taskNeedNewBodyStream = { _, _ in inputStream }            }            delegate[task] = upload            if startRequestsImmediately { upload.resume() }            return upload        } catch {            return upload(uploadable, failedWith: error)        }    }    private func upload(_ uploadable: UploadRequest.Uploadable?, failedWith error: Error) -> UploadRequest {        var uploadTask: Request.RequestTask = .upload(nil, nil)        if let uploadable = uploadable {            uploadTask = .upload(uploadable, nil)        }        let underlyingError = error.underlyingAdaptError ?? error        let upload = UploadRequest(session: session, requestTask: uploadTask, error: underlyingError)        if let retrier = retrier, error is AdaptError {            allowRetrier(retrier, toRetry: upload, with: underlyingError)        } else {            if startRequestsImmediately { upload.resume() }        }        return upload    }#if !os(watchOS)    // MARK: - Stream Request    // MARK: Hostname and Port    /// Creates a `StreamRequest` for bidirectional streaming using the `hostname` and `port`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter hostName: The hostname of the server to connect to.    /// - parameter port:     The port of the server to connect to.    ///    /// - returns: The created `StreamRequest`.    @discardableResult    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)    open func stream(withHostName hostName: String, port: Int) -> StreamRequest {        return stream(.stream(hostName: hostName, port: port))    }    // MARK: NetService    /// Creates a `StreamRequest` for bidirectional streaming using the `netService`.    ///    /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.    ///    /// - parameter netService: The net service used to identify the endpoint.    ///    /// - returns: The created `StreamRequest`.    @discardableResult    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)    open func stream(with netService: NetService) -> StreamRequest {        return stream(.netService(netService))    }    // MARK: Private - Stream Implementation    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)    private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {        do {            let task = try streamable.task(session: session, adapter: adapter, queue: queue)            let request = StreamRequest(session: session, requestTask: .stream(streamable, task))            delegate[task] = request            if startRequestsImmediately { request.resume() }            return request        } catch {            return stream(failedWith: error)        }    }    @available(iOS 9.0, macOS 10.11, tvOS 9.0, *)    private func stream(failedWith error: Error) -> StreamRequest {        let stream = StreamRequest(session: session, requestTask: .stream(nil, nil), error: error)        if startRequestsImmediately { stream.resume() }        return stream    }#endif    // MARK: - Internal - Retry Request    func retry(_ request: Request) -> Bool {        guard let originalTask = request.originalTask else { return false }        do {            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)            if let originalTask = request.task {                delegate[originalTask] = nil // removes the old request to avoid endless growth            }            request.delegate.task = task // resets all task delegate data            request.retryCount += 1            request.startTime = CFAbsoluteTimeGetCurrent()            request.endTime = nil            task.resume()            return true        } catch {            request.delegate.error = error.underlyingAdaptError ?? error            return false        }    }    private func allowRetrier(_ retrier: RequestRetrier, toRetry request: Request, with error: Error) {        DispatchQueue.utility.async { [weak self] in            guard let strongSelf = self else { return }            retrier.should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in                guard let strongSelf = self else { return }                guard shouldRetry else {                    if strongSelf.startRequestsImmediately { request.resume() }                    return                }                DispatchQueue.utility.after(timeDelay) {                    guard let strongSelf = self else { return }                    let retrySucceeded = strongSelf.retry(request)                    if retrySucceeded, let task = request.task {                        strongSelf.delegate[task] = request                    } else {                        if strongSelf.startRequestsImmediately { request.resume() }                    }                }            }        }    }}
 |