SessionManager.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  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 `Request` 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: Request, streamingFromDisk: Bool, streamFileURL: URL?)
  37. case failure(Error)
  38. }
  39. private enum Downloadable {
  40. case request(URLRequest)
  41. case resumeData(Data)
  42. }
  43. private enum Uploadable {
  44. case data(Data, URLRequest)
  45. case file(URL, URLRequest)
  46. case stream(InputStream, URLRequest)
  47. }
  48. #if !os(watchOS)
  49. private enum Streamable {
  50. case stream(String, Int)
  51. case netService(NetService)
  52. }
  53. #endif
  54. // MARK: - Properties
  55. /// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use
  56. /// directly for any ad hoc requests.
  57. open static let `default`: SessionManager = {
  58. let configuration = URLSessionConfiguration.default
  59. configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
  60. return SessionManager(configuration: configuration)
  61. }()
  62. /// Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.
  63. open static let defaultHTTPHeaders: [String: String] = {
  64. // Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
  65. let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
  66. // Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
  67. let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in
  68. let quality = 1.0 - (Double(index) * 0.1)
  69. return "\(languageCode);q=\(quality)"
  70. }.joined(separator: ", ")
  71. // User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
  72. let userAgent: String = {
  73. if let info = Bundle.main.infoDictionary {
  74. let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
  75. let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
  76. let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
  77. let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
  78. let osNameVersion: String = {
  79. let version = ProcessInfo.processInfo.operatingSystemVersion
  80. let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
  81. let osName: String = {
  82. #if os(iOS)
  83. return "iOS"
  84. #elseif os(watchOS)
  85. return "watchOS"
  86. #elseif os(tvOS)
  87. return "tvOS"
  88. #elseif os(OSX)
  89. return "OS X"
  90. #elseif os(Linux)
  91. return "Linux"
  92. #else
  93. return "Unknown"
  94. #endif
  95. }()
  96. return "\(osName) \(versionString)"
  97. }()
  98. return "\(executable)/\(bundle) (\(appVersion)/\(appBuild)); \(osNameVersion))"
  99. }
  100. return "Alamofire"
  101. }()
  102. return [
  103. "Accept-Encoding": acceptEncoding,
  104. "Accept-Language": acceptLanguage,
  105. "User-Agent": userAgent
  106. ]
  107. }()
  108. /// Default memory threshold used when encoding `MultipartFormData` in bytes.
  109. open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000
  110. /// The underlying session.
  111. open let session: URLSession
  112. /// The session delegate handling all the task and session delegate callbacks.
  113. open let delegate: SessionDelegate
  114. /// Whether to start requests immediately after being constructed. `true` by default.
  115. open var startRequestsImmediately: Bool = true
  116. /// The background completion handler closure provided by the UIApplicationDelegate
  117. /// `application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
  118. /// completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
  119. /// will automatically call the handler.
  120. ///
  121. /// If you need to handle your own events before the handler is called, then you need to override the
  122. /// SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
  123. ///
  124. /// `nil` by default.
  125. open var backgroundCompletionHandler: (() -> Void)?
  126. let queue = DispatchQueue(label: "Alamofire Session Manager Queue")
  127. // MARK: - Lifecycle
  128. /// Creates an instance with the specified `configuration`, `delegate` and `serverTrustPolicyManager`.
  129. ///
  130. /// - parameter configuration: The configuration used to construct the managed session.
  131. /// `URLSessionConfiguration.default` by default.
  132. /// - parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by
  133. /// default.
  134. /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
  135. /// challenges. `nil` by default.
  136. ///
  137. /// - returns: The new `SessionManager` instance.
  138. public init(
  139. configuration: URLSessionConfiguration = URLSessionConfiguration.default,
  140. delegate: SessionDelegate = SessionDelegate(),
  141. serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
  142. {
  143. self.delegate = delegate
  144. self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
  145. commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
  146. }
  147. /// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
  148. ///
  149. /// - parameter session: The URL session.
  150. /// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
  151. /// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
  152. /// challenges. `nil` by default.
  153. ///
  154. /// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.
  155. public init?(
  156. session: URLSession,
  157. delegate: SessionDelegate,
  158. serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
  159. {
  160. guard delegate === session.delegate else { return nil }
  161. self.delegate = delegate
  162. self.session = session
  163. commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
  164. }
  165. private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
  166. session.serverTrustPolicyManager = serverTrustPolicyManager
  167. delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
  168. guard let strongSelf = self else { return }
  169. DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
  170. }
  171. }
  172. deinit {
  173. session.invalidateAndCancel()
  174. }
  175. // MARK: - Data Request
  176. /// Creates a data `Request` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  177. /// `parameters`, `encoding` and `headers`.
  178. ///
  179. /// - parameter urlString: The URL string.
  180. /// - parameter method: The HTTP method.
  181. /// - parameter parameters: The parameters. `nil` by default.
  182. /// - parameter encoding: The parameter encoding. `.url` by default.
  183. /// - parameter headers: The HTTP headers. `nil` by default.
  184. ///
  185. /// - returns: The created data `Request`.
  186. @discardableResult
  187. open func request(
  188. _ urlString: URLStringConvertible,
  189. withMethod method: HTTPMethod,
  190. parameters: [String: Any]? = nil,
  191. encoding: ParameterEncoding = .url,
  192. headers: [String: String]? = nil)
  193. -> Request
  194. {
  195. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  196. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  197. return request(encodedURLRequest)
  198. }
  199. /// Creates a data `Request` to retrieve the contents of a URL based on the specified `urlRequest`.
  200. ///
  201. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  202. ///
  203. /// - parameter urlRequest: The URL request
  204. ///
  205. /// - returns: The created data `Request`.
  206. open func request(_ urlRequest: URLRequestConvertible) -> Request {
  207. var dataTask: URLSessionDataTask!
  208. queue.sync { dataTask = self.session.dataTask(with: urlRequest.urlRequest) }
  209. let request = Request(session: session, task: dataTask)
  210. delegate[request.delegate.task] = request
  211. if startRequestsImmediately {
  212. request.resume()
  213. }
  214. return request
  215. }
  216. // MARK: - Download Request
  217. // MARK: URL Request
  218. /// Creates a download `Request` to retrieve the contents of a URL based on the specified `urlString`, `method`,
  219. /// `parameters`, `encoding`, `headers` and save them to the `destination`.
  220. ///
  221. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  222. ///
  223. /// - parameter urlString: The URL string.
  224. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  225. /// - parameter method: The HTTP method.
  226. /// - parameter parameters: The parameters. `nil` by default.
  227. /// - parameter encoding: The parameter encoding. `.url` by default.
  228. /// - parameter headers: The HTTP headers. `nil` by default.
  229. ///
  230. /// - returns: The created download `Request`.
  231. @discardableResult
  232. open func download(
  233. _ urlString: URLStringConvertible,
  234. to destination: Request.DownloadFileDestination,
  235. withMethod method: HTTPMethod,
  236. parameters: [String: Any]? = nil,
  237. encoding: ParameterEncoding = .url,
  238. headers: [String: String]? = nil)
  239. -> Request
  240. {
  241. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  242. let encodedURLRequest = encoding.encode(urlRequest, parameters: parameters).0
  243. return download(encodedURLRequest, to: destination)
  244. }
  245. /// Creates a download `Request` to retrieve the contents of a URL based on the specified `urlRequest` and save
  246. /// them to the `destination`.
  247. ///
  248. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  249. ///
  250. /// - parameter urlRequest: The URL request
  251. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  252. ///
  253. /// - returns: The created download `Request`.
  254. @discardableResult
  255. open func download(
  256. _ urlRequest: URLRequestConvertible,
  257. to destination: Request.DownloadFileDestination)
  258. -> Request
  259. {
  260. return download(.request(urlRequest.urlRequest), to: destination)
  261. }
  262. // MARK: Resume Data
  263. /// Creates a download `Request` from the `resumeData` produced from a previous request cancellation to retrieve
  264. /// the contents of the original request and save them to the `destination`.
  265. ///
  266. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  267. ///
  268. /// - parameter resumeData: The resume data. This is an opaque data blob produced by `URLSessionDownloadTask`
  269. /// when a task is cancelled. See `URLSession -downloadTask(withResumeData:)` for
  270. /// additional information.
  271. /// - parameter destination: The closure used to determine the destination of the downloaded file.
  272. ///
  273. /// - returns: The created download `Request`.
  274. @discardableResult
  275. open func download(resourceWithin resumeData: Data, to destination: Request.DownloadFileDestination) -> Request {
  276. return download(.resumeData(resumeData), to: destination)
  277. }
  278. // MARK: Private - Download Implementation
  279. private func download(
  280. _ downloadable: Downloadable,
  281. to destination: Request.DownloadFileDestination)
  282. -> Request
  283. {
  284. var downloadTask: URLSessionDownloadTask!
  285. switch downloadable {
  286. case .request(let request):
  287. queue.sync {
  288. downloadTask = self.session.downloadTask(with: request)
  289. }
  290. case .resumeData(let resumeData):
  291. queue.sync {
  292. downloadTask = self.session.downloadTask(withResumeData: resumeData)
  293. }
  294. }
  295. let request = Request(session: session, task: downloadTask)
  296. if let downloadDelegate = request.delegate as? DownloadTaskDelegate {
  297. downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, downloadTask, URL in
  298. return destination(URL, downloadTask.response as! HTTPURLResponse)
  299. }
  300. }
  301. delegate[request.delegate.task] = request
  302. if startRequestsImmediately {
  303. request.resume()
  304. }
  305. return request
  306. }
  307. // MARK: - Upload Request
  308. // MARK: File
  309. /// Creates an upload `Request` from the specified `method`, `urlString` and `headers` for uploading the `file`.
  310. ///
  311. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  312. ///
  313. /// - parameter file: The file to upload.
  314. /// - parameter method: The HTTP method.
  315. /// - parameter urlString: The URL string.
  316. /// - parameter headers: The HTTP headers. `nil` by default.
  317. ///
  318. /// - returns: The created upload `Request`.
  319. @discardableResult
  320. open func upload(
  321. _ fileURL: URL,
  322. to urlString: URLStringConvertible,
  323. withMethod method: HTTPMethod,
  324. headers: [String: String]? = nil)
  325. -> Request
  326. {
  327. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  328. return upload(fileURL, with: urlRequest)
  329. }
  330. /// Creates a upload `Request` from the specified `urlRequest` for uploading the `file`.
  331. ///
  332. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  333. ///
  334. /// - parameter file: The file to upload.
  335. /// - parameter urlRequest: The URL request.
  336. ///
  337. /// - returns: The created upload `Request`.
  338. @discardableResult
  339. open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> Request {
  340. return upload(.file(fileURL, urlRequest.urlRequest))
  341. }
  342. // MARK: Data
  343. /// Creates an upload `Request` from the specified `method`, `urlString` and `headers` for uploading the `data`.
  344. ///
  345. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  346. ///
  347. /// - parameter data: The data to upload.
  348. /// - parameter urlString: The URL string.
  349. /// - parameter method: The HTTP method.
  350. /// - parameter headers: The HTTP headers. `nil` by default.
  351. ///
  352. /// - returns: The created upload `Request`.
  353. @discardableResult
  354. open func upload(
  355. _ data: Data,
  356. to urlString: URLStringConvertible,
  357. withMethod method: HTTPMethod,
  358. headers: [String: String]? = nil)
  359. -> Request
  360. {
  361. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  362. return upload(data, with: urlRequest)
  363. }
  364. /// Creates an upload `Request` from the specified `urlRequest` for uploading the `data`.
  365. ///
  366. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  367. ///
  368. /// - parameter data: The data to upload.
  369. /// - parameter urlRequest: The URL request.
  370. ///
  371. /// - returns: The created upload `Request`.
  372. @discardableResult
  373. open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> Request {
  374. return upload(.data(data, urlRequest.urlRequest))
  375. }
  376. // MARK: InputStream
  377. /// Creates an upload `Request` from the specified `method`, `urlString` and `headers` for uploading the `stream`.
  378. ///
  379. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  380. ///
  381. /// - parameter stream: The stream to upload.
  382. /// - parameter urlString: The URL string.
  383. /// - parameter method: The HTTP method.
  384. /// - parameter headers: The HTTP headers. `nil` by default.
  385. ///
  386. /// - returns: The created upload `Request`.
  387. @discardableResult
  388. open func upload(
  389. _ stream: InputStream,
  390. to urlString: URLStringConvertible,
  391. withMethod method: HTTPMethod,
  392. headers: [String: String]? = nil)
  393. -> Request
  394. {
  395. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  396. return upload(stream, with: urlRequest)
  397. }
  398. /// Creates an upload `Request` from the specified `urlRequest` for uploading the `stream`.
  399. ///
  400. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  401. ///
  402. /// - parameter stream: The stream to upload.
  403. /// - parameter urlRequest: The URL request.
  404. ///
  405. /// - returns: The created upload `Request`.
  406. @discardableResult
  407. open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> Request {
  408. return upload(.stream(stream, urlRequest.urlRequest))
  409. }
  410. // MARK: MultipartFormData
  411. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  412. /// upload `Request` using the `method`, `urlString` and `headers`.
  413. ///
  414. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  415. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  416. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  417. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  418. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  419. /// used for larger payloads such as video content.
  420. ///
  421. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  422. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  423. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  424. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  425. /// technique was used.
  426. ///
  427. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  428. ///
  429. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  430. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  431. /// `multipartFormDataEncodingMemoryThreshold` by default.
  432. /// - parameter urlString: The URL string.
  433. /// - parameter method: The HTTP method.
  434. /// - parameter headers: The HTTP headers. `nil` by default.
  435. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  436. open func upload(
  437. multipartFormData: @escaping (MultipartFormData) -> Void,
  438. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  439. to urlString: URLStringConvertible,
  440. withMethod method: HTTPMethod,
  441. headers: [String: String]? = nil,
  442. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  443. {
  444. let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
  445. return upload(
  446. multipartFormData: multipartFormData,
  447. usingThreshold: encodingMemoryThreshold,
  448. with: urlRequest,
  449. encodingCompletion: encodingCompletion
  450. )
  451. }
  452. /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
  453. /// upload `Request` using the `urlRequest`.
  454. ///
  455. /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
  456. /// payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
  457. /// efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
  458. /// be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
  459. /// footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
  460. /// used for larger payloads such as video content.
  461. ///
  462. /// The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
  463. /// or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
  464. /// encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
  465. /// during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
  466. /// technique was used.
  467. ///
  468. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  469. ///
  470. /// - parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
  471. /// - parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
  472. /// `multipartFormDataEncodingMemoryThreshold` by default.
  473. /// - parameter urlRequest: The URL request.
  474. /// - parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
  475. open func upload(
  476. multipartFormData: @escaping (MultipartFormData) -> Void,
  477. usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
  478. with urlRequest: URLRequestConvertible,
  479. encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
  480. {
  481. DispatchQueue.global(qos: .utility).async {
  482. let formData = MultipartFormData()
  483. multipartFormData(formData)
  484. var urlRequestWithContentType = urlRequest.urlRequest
  485. urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
  486. let isBackgroundSession = self.session.configuration.identifier != nil
  487. if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
  488. do {
  489. let data = try formData.encode()
  490. let encodingResult = MultipartFormDataEncodingResult.success(
  491. request: self.upload(data, with: urlRequestWithContentType),
  492. streamingFromDisk: false,
  493. streamFileURL: nil
  494. )
  495. DispatchQueue.main.async { encodingCompletion?(encodingResult) }
  496. } catch {
  497. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  498. }
  499. } else {
  500. let fileManager = FileManager.default
  501. let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
  502. let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
  503. let fileName = UUID().uuidString
  504. let fileURL = directoryURL.appendingPathComponent(fileName)
  505. do {
  506. try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
  507. try formData.writeEncodedData(to: fileURL)
  508. DispatchQueue.main.async {
  509. let encodingResult = MultipartFormDataEncodingResult.success(
  510. request: self.upload(fileURL, with: urlRequestWithContentType),
  511. streamingFromDisk: true,
  512. streamFileURL: fileURL
  513. )
  514. encodingCompletion?(encodingResult)
  515. }
  516. } catch {
  517. DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
  518. }
  519. }
  520. }
  521. }
  522. // MARK: Private - Upload Implementation
  523. private func upload(_ uploadable: Uploadable) -> Request {
  524. var uploadTask: URLSessionUploadTask!
  525. var HTTPBodyStream: InputStream?
  526. switch uploadable {
  527. case .data(let data, let request):
  528. queue.sync {
  529. uploadTask = self.session.uploadTask(with: request, from: data)
  530. }
  531. case .file(let fileURL, let request):
  532. queue.sync {
  533. uploadTask = self.session.uploadTask(with: request, fromFile: fileURL)
  534. }
  535. case .stream(let stream, let request):
  536. queue.sync {
  537. uploadTask = self.session.uploadTask(withStreamedRequest: request)
  538. }
  539. HTTPBodyStream = stream
  540. }
  541. let request = Request(session: session, task: uploadTask)
  542. if HTTPBodyStream != nil {
  543. request.delegate.taskNeedNewBodyStream = { _, _ in
  544. return HTTPBodyStream
  545. }
  546. }
  547. delegate[request.delegate.task] = request
  548. if startRequestsImmediately {
  549. request.resume()
  550. }
  551. return request
  552. }
  553. #if !os(watchOS)
  554. // MARK: - Stream Request
  555. // MARK: Hostname and Port
  556. /// Creates a stream `Request` for bidirectional streaming using the `hostname` and `port`.
  557. ///
  558. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  559. ///
  560. /// - parameter hostName: The hostname of the server to connect to.
  561. /// - parameter port: The port of the server to connect to.
  562. ///
  563. /// - returns: The created stream `Request`.
  564. @discardableResult
  565. open func stream(withHostName hostName: String, port: Int) -> Request {
  566. return stream(.stream(hostName, port))
  567. }
  568. // MARK: NetService
  569. /// Creates a stream `Request` for bidirectional streaming using the `netService`.
  570. ///
  571. /// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
  572. ///
  573. /// - parameter netService: The net service used to identify the endpoint.
  574. ///
  575. /// - returns: The created stream `Request`.
  576. @discardableResult
  577. open func stream(with netService: NetService) -> Request {
  578. return stream(.netService(netService))
  579. }
  580. // MARK: Private - Stream Implementation
  581. private func stream(_ streamable: Streamable) -> Request {
  582. var streamTask: URLSessionStreamTask!
  583. switch streamable {
  584. case .stream(let hostName, let port):
  585. queue.sync {
  586. streamTask = self.session.streamTask(withHostName: hostName, port: port)
  587. }
  588. case .netService(let netService):
  589. queue.sync {
  590. streamTask = self.session.streamTask(with: netService)
  591. }
  592. }
  593. let request = Request(session: session, task: streamTask)
  594. delegate[request.delegate.task] = request
  595. if startRequestsImmediately {
  596. request.resume()
  597. }
  598. return request
  599. }
  600. #endif
  601. }