added iOS source code
[wl-app.git] / iOS / Pods / Alamofire / Source / SessionManager.swift
diff --git a/iOS/Pods/Alamofire/Source/SessionManager.swift b/iOS/Pods/Alamofire/Source/SessionManager.swift
new file mode 100644 (file)
index 0000000..0ff677b
--- /dev/null
@@ -0,0 +1,892 @@
+//
+//  SessionManager.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 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.
+    open 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.
+    open 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.
+    open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
+
+    /// The underlying session.
+    open let session: URLSession
+
+    /// The session delegate handling all the task and session delegate callbacks.
+    open 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,
+        encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
+    {
+        do {
+            let urlRequest = try URLRequest(url: url, method: method, headers: headers)
+
+            return upload(
+                multipartFormData: multipartFormData,
+                usingThreshold: encodingMemoryThreshold,
+                with: urlRequest,
+                encodingCompletion: encodingCompletion
+            )
+        } catch {
+            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,
+        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
+                    )
+
+                    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
+                        }
+                    }
+
+                    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
+                    }
+                }
+
+                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)
+
+            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() }
+                    }
+                }
+            }
+        }
+    }
+}