SessionManager.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. //
  2. // SessionManager.swift
  3. //
  4. // Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. /// Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`.
  26. open class SessionManager {
  27. // MARK: - Helper Types
  28. /// Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
  29. /// associated values.
  30. ///
  31. /// - Success: Represents a successful `MultipartFormData` encoding and contains the new `UploadRequest` along with
  32. /// streaming information.
  33. /// - Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
  34. /// error.
  35. public enum MultipartFormDataEncodingResult {
  36. case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
  37. case failure(Error)
  38. }
  39. // MARK: - Properties
  40. /// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use
  41. /// directly for any ad hoc requests.
  42. open static let `default`: SessionManager = {
  43. let configuration = URLSessionConfiguration.default
  44. configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
  45. return SessionManager(configuration: configuration)
  46. }()
  47. /// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.
  48. open static let defaultHTTPHeaders: [String: String] = {
  49. // Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
  50. let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
  51. // Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
  52. let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in
  53. let quality = 1.0 - (Double(index) * 0.1)
  54. return "\(languageCode);q=\(quality)"
  55. }.joined(separator: ", ")
  56. // User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
  57. let userAgent: String = {
  58. if let info = Bundle.main.infoDictionary {
  59. let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
  60. let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
  61. let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
  62. let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
  63. let osNameVersion: String = {
  64. let version = ProcessInfo.processInfo.operatingSystemVersion
  65. let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
  66. let osName: String = {
  67. #if os(iOS)
  68. return "iOS"
  69. #elseif os(watchOS)
  70. return "watchOS"
  71. #elseif os(tvOS)
  72. return "tvOS"
  73. #elseif os(OSX)
  74. return "OS X"
  75. #elseif os(Linux)
  76. return "Linux"
  77. #else
  78. return "Unknown"
  79. #endif
  80. }()
  81. return "\(osName) \(versionString)"
  82. }()
  83. return "\(executable)/\(bundle) (\(appVersion)/\(appBuild)); \(osNameVersion))"
  84. }
  85. return "Alamofire"
  86. }()
  87. return [
  88. "Accept-Encoding": acceptEncoding,
  89. "Accept-Language": acceptLanguage,
  90. "User-Agent": userAgent
  91. ]
  92. }()
  93. /// Default memory threshold used when encoding `MultipartFormData` in bytes.
  94. open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
  95. /// The underlying session.
  96. open let session: URLSession
  97. /// The session delegate handling all the task and session delegate callbacks.
  98. open let delegate: SessionDelegate
  99. /// Whether to start requests immediately after being constructed. `true` by default.
  100. open var startRequestsImmediately: Bool = true
  101. /// The request adapter called each time a new request is created.
  102. open var adapter: RequestAdapter?
  103. /// The request retrier called each time a request encounters an error to determine whether to retry the request.
  104. open var retrier: RequestRetrier? {
  105. get { return delegate.retrier }
  106. set { delegate.retrier = newValue }
  107. }
  108. /// The background completion handler closure provided by the UIApplicationDelegate
  109. /// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
  110. /// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
  111. /// will automatically call the handler.
  112. ///
  113. /// If you need to handle your own events before the handler is called, then you need to override the
  114. /// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
  115. ///
  116. /// `nil` by default.
  117. open var backgroundCompletionHandler: (() -> Void)?
  118. let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
  119. // MARK: - Lifecycle
  120. /// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.
  121. ///
  122. /// - parameter configuration: The configuration used to construct the managed session.
  123. /// `URLSessionConfiguration.default` by default.
  124. /// - parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by
  125. /// default.
  126. /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
  127. /// challenges. `nil` by default.
  128. ///
  129. /// - returns: The new `SessionManager` instance.
  130. public init(
  131. configuration: URLSessionConfiguration = URLSessionConfiguration.default,
  132. delegate: SessionDelegate = SessionDelegate(),
  133. serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
  134. {
  135. self.delegate = delegate
  136. self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
  137. commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
  138. }
  139. /// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
  140. ///
  141. /// - parameter session: The URL session.
  142. /// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
  143. /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
  144. /// challenges. `nil` by default.
  145. ///
  146. /// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.
  147. public init?(
  148. session: URLSession,
  149. delegate: SessionDelegate,
  150. serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
  151. {
  152. guard delegate === session.delegate else { return nil }
  153. self.delegate = delegate
  154. self.session = session
  155. commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
  156. }
  157. private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
  158. session.serverTrustPolicyManager = serverTrustPolicyManager
  159. delegate.sessionManager = self
  160. delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
  161. guard let strongSelf = self else { return }
  162. DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
  163. }
  164. }
  165. deinit {
  166. session.invalidateAndCancel()
  167. }
  168. // MARK: - Data Request
  169. /// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  170. /// `parameters`, `encoding` and `headers`.
  171. ///
  172. /// - parameter urlString: The URL string.
  173. /// - parameter method: The HTTP method.
  174. /// - parameter parameters: The parameters. `nil` by default.
  175. /// - parameter encoding: The parameter encoding. `.url` by default.
  176. /// - parameter headers: The HTTP headers. `nil` by default.
  177. ///
  178. /// - returns: The created `DataRequest`.
  179. @discardableResult
  180. open func request(
  181. _ urlString: URLStringConvertible,
  182. withMethod method: HTTPMethod,
  183. parameters: [String: Any]? = nil,
  184. encoding: ParameterEncoding = .url,
  185. headers: [String: String]? = nil)
  186. -> DataRequest
  187. {
  188. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  189. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  190. return request(encodedURLRequest)
  191. }
  192. /// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.
  193. ///
  194. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  195. ///
  196. /// - parameter urlRequest: The URL request
  197. ///
  198. /// - returns: The created `DataRequest`.
  199. open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
  200. let originalRequest = urlRequest.urlRequest
  201. let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
  202. let task = originalTask.task(session: session, adapter: adapter, queue: queue)
  203. let request = DataRequest(session: session, task: task, originalTask: originalTask)
  204. delegate[request.delegate.task] = request
  205. if startRequestsImmediately { request.resume() }
  206. return request
  207. }
  208. // MARK: - Download Request
  209. // MARK: URL Request
  210. /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  211. /// `parameters`, `encoding`, `headers` and save them to the `destination`.
  212. ///
  213. /// If `destination` is not specified, the contents will remain in the temporary location determined by the
  214. /// URL session.
  215. ///
  216. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  217. ///
  218. /// - parameter urlString: The URL string.
  219. /// - parameter method: The HTTP method. `.get` by default.
  220. /// - parameter parameters: The parameters. `nil` by default.
  221. /// - parameter encoding: The parameter encoding. `.url` by default.
  222. /// - parameter headers: The HTTP headers. `nil` by default.
  223. /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
  224. ///
  225. /// - returns: The created `DownloadRequest`.
  226. @discardableResult
  227. open func download(
  228. _ urlString: URLStringConvertible,
  229. method: HTTPMethod = .get,
  230. parameters: [String: Any]? = nil,
  231. encoding: ParameterEncoding = .url,
  232. headers: [String: String]? = nil,
  233. to destination: DownloadRequest.DownloadFileDestination? = nil)
  234. -> DownloadRequest
  235. {
  236. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  237. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  238. return download(resource: encodedURLRequest, to: destination)
  239. }
  240. /// Creates a `DownloadRequest` to retrieve the contents of a URL based on the specified `urlRequest` and save
  241. /// them to the `destination`.
  242. ///
  243. /// If `destination` is not specified, the contents will remain in the temporary location determined by the
  244. /// URL session.
  245. ///
  246. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  247. ///
  248. /// - parameter urlRequest: The URL request
  249. /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
  250. ///
  251. /// - returns: The created `DownloadRequest`.
  252. @discardableResult
  253. open func download(
  254. resource urlRequest: URLRequestConvertible,
  255. to destination: DownloadRequest.DownloadFileDestination? = nil)
  256. -> DownloadRequest
  257. {
  258. return download(.request(urlRequest.urlRequest), to: destination)
  259. }
  260. // MARK: Resume Data
  261. /// Creates a `DownloadRequest` from the `resumeData` produced from a previous request cancellation to retrieve
  262. /// the contents of the original request and save them to the `destination`.
  263. ///
  264. /// If `destination` is not specified, the contents will remain in the temporary location determined by the
  265. /// URL session.
  266. ///
  267. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  268. ///
  269. /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
  270. /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for
  271. /// additional information.
  272. /// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
  273. ///
  274. /// - returns: The created `DownloadRequest`.
  275. @discardableResult
  276. open func download(
  277. resourceWithin resumeData: Data,
  278. to destination: DownloadRequest.DownloadFileDestination? = nil)
  279. -> DownloadRequest
  280. {
  281. return download(.resumeData(resumeData), to: destination)
  282. }
  283. // MARK: Private - Download Implementation
  284. private func download(
  285. _ downloadable: DownloadRequest.Downloadable,
  286. to destination: DownloadRequest.DownloadFileDestination?)
  287. -> DownloadRequest
  288. {
  289. let task = downloadable.task(session: session, adapter: adapter, queue: queue)
  290. let request = DownloadRequest(session: session, task: task, originalTask: downloadable)
  291. request.downloadDelegate.destination = destination
  292. delegate[request.delegate.task] = request
  293. if startRequestsImmediately { request.resume() }
  294. return request
  295. }
  296. // MARK: - Upload Request
  297. // MARK: File
  298. /// Creates an `UploadRequest` from the specified `method`, `urlString` and `headers` for uploading the `file`.
  299. ///
  300. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  301. ///
  302. /// - parameter file: The file to upload.
  303. /// - parameter method: The HTTP method.
  304. /// - parameter urlString: The URL string.
  305. /// - parameter headers: The HTTP headers. `nil` by default.
  306. ///
  307. /// - returns: The created `UploadRequest`.
  308. @discardableResult
  309. open func upload(
  310. _ fileURL: URL,
  311. to urlString: URLStringConvertible,
  312. withMethod method: HTTPMethod,
  313. headers: [String: String]? = nil)
  314. -> UploadRequest
  315. {
  316. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  317. return upload(fileURL, with: urlRequest)
  318. }
  319. /// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`.
  320. ///
  321. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  322. ///
  323. /// - parameter file: The file to upload.
  324. /// - parameter urlRequest: The URL request.
  325. ///
  326. /// - returns: The created `UploadRequest`.
  327. @discardableResult
  328. open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
  329. return upload(.file(fileURL, urlRequest.urlRequest))
  330. }
  331. // MARK: Data
  332. /// Creates an `UploadRequest` from the specified `method`, `urlString` and `headers` for uploading the `data`.
  333. ///
  334. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  335. ///
  336. /// - parameter data: The data to upload.
  337. /// - parameter urlString: The URL string.
  338. /// - parameter method: The HTTP method.
  339. /// - parameter headers: The HTTP headers. `nil` by default.
  340. ///
  341. /// - returns: The created `UploadRequest`.
  342. @discardableResult
  343. open func upload(
  344. _ data: Data,
  345. to urlString: URLStringConvertible,
  346. withMethod method: HTTPMethod,
  347. headers: [String: String]? = nil)
  348. -> UploadRequest
  349. {
  350. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  351. return upload(data, with: urlRequest)
  352. }
  353. /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`.
  354. ///
  355. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  356. ///
  357. /// - parameter data: The data to upload.
  358. /// - parameter urlRequest: The URL request.
  359. ///
  360. /// - returns: The created `UploadRequest`.
  361. @discardableResult
  362. open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
  363. return upload(.data(data, urlRequest.urlRequest))
  364. }
  365. // MARK: InputStream
  366. /// Creates an `UploadRequest` from the specified `method`, `urlString` and `headers` for uploading the `stream`.
  367. ///
  368. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  369. ///
  370. /// - parameter stream: The stream to upload.
  371. /// - parameter urlString: The URL string.
  372. /// - parameter method: The HTTP method.
  373. /// - parameter headers: The HTTP headers. `nil` by default.
  374. ///
  375. /// - returns: The created `UploadRequest`.
  376. @discardableResult
  377. open func upload(
  378. _ stream: InputStream,
  379. to urlString: URLStringConvertible,
  380. withMethod method: HTTPMethod,
  381. headers: [String: String]? = nil)
  382. -> UploadRequest
  383. {
  384. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  385. return upload(stream, with: urlRequest)
  386. }
  387. /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`.
  388. ///
  389. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  390. ///
  391. /// - parameter stream: The stream to upload.
  392. /// - parameter urlRequest: The URL request.
  393. ///
  394. /// - returns: The created `UploadRequest`.
  395. @discardableResult
  396. open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
  397. return upload(.stream(stream, urlRequest.urlRequest))
  398. }
  399. // MARK: MultipartFormData
  400. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  401. /// `UploadRequest` using the `method`, `urlString` and `headers`.
  402. ///
  403. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  404. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  405. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  406. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  407. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  408. /// used for larger payloads such as video content.
  409. ///
  410. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  411. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  412. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  413. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  414. /// technique was used.
  415. ///
  416. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  417. ///
  418. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  419. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  420. /// `multipartFormDataEncodingMemoryThreshold` by default.
  421. /// - parameter urlString: The URL string.
  422. /// - parameter method: The HTTP method.
  423. /// - parameter headers: The HTTP headers. `nil` by default.
  424. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  425. open func upload(
  426. multipartFormData: @escaping (MultipartFormData) -> Void,
  427. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  428. to urlString: URLStringConvertible,
  429. withMethod method: HTTPMethod,
  430. headers: [String: String]? = nil,
  431. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  432. {
  433. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  434. return upload(
  435. multipartFormData: multipartFormData,
  436. usingThreshold: encodingMemoryThreshold,
  437. with: urlRequest,
  438. encodingCompletion: encodingCompletion
  439. )
  440. }
  441. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  442. /// `UploadRequest` using the `urlRequest`.
  443. ///
  444. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  445. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  446. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  447. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  448. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  449. /// used for larger payloads such as video content.
  450. ///
  451. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  452. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  453. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  454. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  455. /// technique was used.
  456. ///
  457. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  458. ///
  459. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  460. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  461. /// `multipartFormDataEncodingMemoryThreshold` by default.
  462. /// - parameter urlRequest: The URL request.
  463. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  464. open func upload(
  465. multipartFormData: @escaping (MultipartFormData) -> Void,
  466. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  467. with urlRequest: URLRequestConvertible,
  468. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  469. {
  470. DispatchQueue.global(qos: .utility).async {
  471. let formData = MultipartFormData()
  472. multipartFormData(formData)
  473. var urlRequestWithContentType = urlRequest.urlRequest
  474. urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
  475. let isBackgroundSession = self.session.configuration.identifier != nil
  476. if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
  477. do {
  478. let data = try formData.encode()
  479. let encodingResult = MultipartFormDataEncodingResult.success(
  480. request: self.upload(data, with: urlRequestWithContentType),
  481. streamingFromDisk: false,
  482. streamFileURL: nil
  483. )
  484. DispatchQueue.main.async { encodingCompletion?(encodingResult) }
  485. } catch {
  486. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  487. }
  488. } else {
  489. let fileManager = FileManager.default
  490. let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
  491. let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
  492. let fileName = UUID().uuidString
  493. let fileURL = directoryURL.appendingPathComponent(fileName)
  494. do {
  495. try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
  496. try formData.writeEncodedData(to: fileURL)
  497. DispatchQueue.main.async {
  498. let encodingResult = MultipartFormDataEncodingResult.success(
  499. request: self.upload(fileURL, with: urlRequestWithContentType),
  500. streamingFromDisk: true,
  501. streamFileURL: fileURL
  502. )
  503. encodingCompletion?(encodingResult)
  504. }
  505. } catch {
  506. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  507. }
  508. }
  509. }
  510. }
  511. // MARK: Private - Upload Implementation
  512. private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {
  513. let task = uploadable.task(session: session, adapter: adapter, queue: queue)
  514. let request = UploadRequest(session: session, task: task, originalTask: uploadable)
  515. if case let .stream(inputStream, _) = uploadable {
  516. request.delegate.taskNeedNewBodyStream = { _, _ in inputStream }
  517. }
  518. delegate[request.delegate.task] = request
  519. if startRequestsImmediately { request.resume() }
  520. return request
  521. }
  522. #if !os(watchOS)
  523. // MARK: - Stream Request
  524. // MARK: Hostname and Port
  525. /// Creates a `StreamRequest` for bidirectional streaming using the `hostname` and `port`.
  526. ///
  527. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  528. ///
  529. /// - parameter hostName: The hostname of the server to connect to.
  530. /// - parameter port: The port of the server to connect to.
  531. ///
  532. /// - returns: The created `StreamRequest`.
  533. @discardableResult
  534. open func stream(withHostName hostName: String, port: Int) -> StreamRequest {
  535. return stream(.stream(hostName: hostName, port: port))
  536. }
  537. // MARK: NetService
  538. /// Creates a `StreamRequest` for bidirectional streaming using the `netService`.
  539. ///
  540. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  541. ///
  542. /// - parameter netService: The net service used to identify the endpoint.
  543. ///
  544. /// - returns: The created `StreamRequest`.
  545. @discardableResult
  546. open func stream(with netService: NetService) -> StreamRequest {
  547. return stream(.netService(netService))
  548. }
  549. // MARK: Private - Stream Implementation
  550. private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
  551. let task = streamable.task(session: session, adapter: adapter, queue: queue)
  552. let request = StreamRequest(session: session, task: task, originalTask: streamable)
  553. delegate[request.delegate.task] = request
  554. if startRequestsImmediately { request.resume() }
  555. return request
  556. }
  557. #endif
  558. // MARK: - Internal - Retry Request
  559. func retry(_ request: Request) -> Bool {
  560. guard let originalTask = request.originalTask else { return false }
  561. let task = originalTask.task(session: session, adapter: adapter, queue: queue)
  562. request.delegate.task = task // resets all task delegate data
  563. request.startTime = CFAbsoluteTimeGetCurrent()
  564. request.endTime = nil
  565. task.resume()
  566. return true
  567. }
  568. }