Jelajahi Sumber

Allow FileManager to be passed to UploadRequest. (#2898)

* Allow FileManager to be passed to UploadRequest.

* Fix some spelling and inline doc alignment.
Jon Shier 6 tahun lalu
induk
melakukan
31d8afd0fa
5 mengubah file dengan 253 tambahan dan 176 penghapusan
  1. 152 111
      Source/Alamofire.swift
  2. 8 3
      Source/Request.swift
  3. 1 1
      Source/ResponseSerialization.swift
  4. 85 54
      Source/Session.swift
  5. 7 7
      Tests/UploadTests.swift

+ 152 - 111
Source/Alamofire.swift

@@ -28,8 +28,8 @@ import Foundation
 public enum AF {
     // MARK: - Data Request
 
-    /// Creates a `DataRequest` using `Session.default` to retrive the contents of the specified `url`
-    /// using the `method`, `parameters`, `encoding`, and `headers` provided.
+    /// Creates a `DataRequest` using `Session.default` to retrieve the contents of the specified `url` using the
+    /// `method`, `parameters`, `encoding`, and `headers` provided.
     ///
     /// - Parameters:
     ///   - url:           The `URLConvertible` value.
@@ -54,8 +54,8 @@ public enum AF {
                                        interceptor: interceptor)
     }
 
-    /// Creates a `DataRequest` using `Session.default` to retrive the contents of the specified `url`
-    /// using the `method`, `parameters`, `encoding`, and `headers` provided.
+    /// Creates a `DataRequest` using `Session.default` to retrieve the contents of the specified `url` using the
+    /// `method`, `parameters`, `encoding`, and `headers` provided.
     ///
     /// - Parameters:
     ///   - url:           The `URLConvertible` value.
@@ -206,113 +206,143 @@ public enum AF {
 
     // MARK: - Upload Request
 
-    // MARK: File
+    // MARK: Data
 
-    /// Creates an `UploadRequest` using `Session.default` to upload the contents of the `fileURL` specified
-    /// using the `url`, `method` and `headers` provided.
+    /// Creates an `UploadRequest` for the given `Data`, `URLRequest` components, and `RequestInterceptor`.
     ///
     /// - Parameters:
-    ///   - fileURL:       The `URL` of the file to upload.
-    ///   - url:           The `URLConvertible` value.
-    ///   - method:        The `HTTPMethod`, `.post` by default.
-    ///   - headers:       The `HTTPHeaders`, `nil` by default.
-    ///   - interceptor:   The `RequestInterceptor`, `nil` by default.
-    ///
-    /// - Returns:         The created `UploadRequest`.
-    public static func upload(_ fileURL: URL,
-                              to url: URLConvertible,
+    ///   - data:        The `Data` to upload.
+    ///   - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - method:      `HTTPMethod` for the `URLRequest`. `.post` by default.
+    ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
+    ///
+    /// - Returns:       The created `UploadRequest`.
+    public static func upload(_ data: Data,
+                              to convertible: URLConvertible,
                               method: HTTPMethod = .post,
                               headers: HTTPHeaders? = nil,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return Session.default.upload(fileURL, to: url, method: method, headers: headers, interceptor: interceptor)
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
+        return Session.default.upload(data,
+                                      to: convertible,
+                                      method: method,
+                                      headers: headers,
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 
-    /// Creates an `UploadRequest` using the `Session.default` to upload the contents of the `fileURL` specificed
-    /// using the `urlRequest` provided.
+    /// Creates an `UploadRequest` for the given `Data` using the `URLRequestConvertible` value and `RequestInterceptor`.
     ///
     /// - Parameters:
-    ///   - fileURL:       The `URL` of the file to upload.
-    ///   - urlRequest:    The `URLRequestConvertible` value.
-    ///   - interceptor:   The `RequestInterceptor`, `nil` by default.
+    ///   - data:        The `Data` to upload.
+    ///   - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
-    /// - Returns:         The created `UploadRequest`.
-    public static func upload(_ fileURL: URL,
-                              with urlRequest: URLRequestConvertible,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return Session.default.upload(fileURL, with: urlRequest, interceptor: interceptor)
+    /// - Returns:       The created `UploadRequest`.
+    public static func upload(_ data: Data,
+                              with convertible: URLRequestConvertible,
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
+        return Session.default.upload(data, with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
-    // MARK: Data
+    // MARK: File
 
-    /// Creates an `UploadRequest` using `Session.default` to upload the contents of the `data` specified using
-    /// the `url`, `method` and `headers` provided.
+    /// Creates an `UploadRequest` for the file at the given file `URL`, using a `URLRequest` from the provided
+    /// components and `RequestInterceptor`.
     ///
     /// - Parameters:
-    ///   - data:          The `Data` to upload.
-    ///   - url:           The `URLConvertible` value.
-    ///   - method:        The `HTTPMethod`, `.post` by default.
-    ///   - headers:       The `HTTPHeaders`, `nil` by default.
-    ///   - interceptor:   The `RequestInterceptor`, `nil` by default.
-    ///   - retryPolicies: The `RetryPolicy` types, `[]` by default.
-    ///
-    /// - Returns:         The created `UploadRequest`.
-    public static func upload(_ data: Data,
-                              to url: URLConvertible,
+    ///   - fileURL:     The `URL` of the file to upload.
+    ///   - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - method:      `HTTPMethod` for the `URLRequest`. `.post` by default.
+    ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `UploadRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
+    ///
+    /// - Returns:       The created `UploadRequest`.
+    public static func upload(_ fileURL: URL,
+                              to convertible: URLConvertible,
                               method: HTTPMethod = .post,
                               headers: HTTPHeaders? = nil,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return Session.default.upload(data, to: url, method: method, headers: headers, interceptor: interceptor)
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
+        return Session.default.upload(fileURL,
+                                      to: convertible,
+                                      method: method,
+                                      headers: headers,
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 
-    /// Creates an `UploadRequest` using `Session.default` to upload the contents of the `data` specified using the
-    /// `urlRequest` provided.
+    /// Creates an `UploadRequest` for the file at the given file `URL` using the `URLRequestConvertible` value and
+    /// `RequestInterceptor`.
     ///
     /// - Parameters:
-    ///   - data:          The `Data` to upload.
-    ///   - urlRequest:    The `URLRequestConvertible` value.
-    ///   - interceptor:   The `RequestInterceptor`, `nil` by default.
+    ///   - fileURL:     The `URL` of the file to upload.
+    ///   - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
-    /// - Returns:         The created `UploadRequest`.
-    public static func upload(_ data: Data,
-                              with urlRequest: URLRequestConvertible,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return Session.default.upload(data, with: urlRequest, interceptor: interceptor)
+    /// - Returns:       The created `UploadRequest`.
+    public static func upload(_ fileURL: URL,
+                              with convertible: URLRequestConvertible,
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
+        return Session.default.upload(fileURL, with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     // MARK: InputStream
 
-    /// Creates an `UploadRequest` using `Session.default` to upload the content provided by the `stream` specified
-    /// using the `url`, `method` and `headers` provided.
+    /// Creates an `UploadRequest` from the `InputStream` provided using a `URLRequest` from the provided components and
+    /// `RequestInterceptor`.
     ///
     /// - Parameters:
-    ///   - stream:        The `InputStream` to upload.
-    ///   - url:           The `URLConvertible` value.
-    ///   - method:        The `HTTPMethod`, `.post` by default.
-    ///   - headers:       The `HTTPHeaders`, `nil` by default.
-    ///   - interceptor:   The `RequestInterceptor`, `nil` by default.
-    ///
-    /// - Returns:         The created `UploadRequest`.
+    ///   - stream:      The `InputStream` that provides the data to upload.
+    ///   - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - method:      `HTTPMethod` for the `URLRequest`. `.post` by default.
+    ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
+    ///
+    /// - Returns:       The created `UploadRequest`.
     public static func upload(_ stream: InputStream,
-                              to url: URLConvertible,
+                              to convertible: URLConvertible,
                               method: HTTPMethod = .post,
                               headers: HTTPHeaders? = nil,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return Session.default.upload(stream, to: url, method: method, headers: headers, interceptor: interceptor)
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
+        return Session.default.upload(stream,
+                                      to: convertible,
+                                      method: method,
+                                      headers: headers,
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 
-    /// Creates an `UploadRequest` using `Session.default` to upload the content provided by the `stream`
-    /// specified using the `urlRequest` specified.
+    /// Creates an `UploadRequest` from the provided `InputStream` using the `URLRequestConvertible` value and
+    /// `RequestInterceptor`.
     ///
     /// - Parameters:
-    ///   - stream:        The `InputStream` to upload.
-    ///   - urlRequest:    The `URLRequestConvertible` value.
-    ///   - interceptor:   The `RequestInterceptor`, `nil` by default.
+    ///   - stream:      The `InputStream` that provides the data to upload.
+    ///   - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
-    /// - Returns:         The created `UploadRequest`.
+    /// - Returns:       The created `UploadRequest`.
     public static func upload(_ stream: InputStream,
-                              with urlRequest: URLRequestConvertible,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return Session.default.upload(stream, with: urlRequest, interceptor: interceptor)
+                              with convertible: URLRequestConvertible,
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
+        return Session.default.upload(stream, with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     // MARK: MultipartFormData
@@ -320,7 +350,7 @@ public enum AF {
     /// Creates an `UploadRequest` for the multipart form data built using a closure and sent using the provided
     /// `URLRequest` components and `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -335,36 +365,37 @@ public enum AF {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` building closure.
-    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
-    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by default.
-    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
-    ///                              written to disk before being uploaded.
     ///   - convertible:             `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
+    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
+    ///                              default.
     ///   - method:                  `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:                 `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
+    ///                              written to disk before being uploaded. `.default` instance by default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     public static func upload(multipartFormData: @escaping (MultipartFormData) -> Void,
-                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
-                              fileManager: FileManager = .default,
                               to url: URLConvertible,
+                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                               method: HTTPMethod = .post,
                               headers: HTTPHeaders? = nil,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
         return Session.default.upload(multipartFormData: multipartFormData,
-                                      usingThreshold: encodingMemoryThreshold,
-                                      fileManager: fileManager,
                                       to: url,
+                                      usingThreshold: encodingMemoryThreshold,
                                       method: method,
                                       headers: headers,
-                                      interceptor: interceptor)
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` using a `MultipartFormData` building closure, the provided `URLRequestConvertible`
     /// value, and a `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -379,30 +410,31 @@ public enum AF {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` building closure.
-    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
-    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by default.
-    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
-    ///                              written to disk before being uploaded.
     ///   - request:                 `URLRequestConvertible` value to be used to create the `URLRequest`.
+    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
+    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
+    ///                              default.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
+    ///                              written to disk before being uploaded. `.default` instance by default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     public static func upload(multipartFormData: @escaping (MultipartFormData) -> Void,
-                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
-                              fileManager: FileManager = .default,
                               with request: URLRequestConvertible,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
         return Session.default.upload(multipartFormData: multipartFormData,
-                                      usingThreshold: encodingMemoryThreshold,
-                                      fileManager: fileManager,
                                       with: request,
-                                      interceptor: interceptor)
+                                      usingThreshold: encodingMemoryThreshold,
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the provided `URLRequest` components
     /// and `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -417,32 +449,37 @@ public enum AF {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` instance to upload.
-    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
-    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by default.
     ///   - url:                     `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
+    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
+    ///                              default.
     ///   - method:                  `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:                 `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
+    ///                              written to disk before being uploaded. `.default` instance by default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     public static func upload(multipartFormData: MultipartFormData,
-                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                               to url: URLConvertible,
+                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                               method: HTTPMethod = .post,
                               headers: HTTPHeaders? = nil,
-                              interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
         return Session.default.upload(multipartFormData: multipartFormData,
-                                      usingThreshold: encodingMemoryThreshold,
                                       to: url,
+                                      usingThreshold: encodingMemoryThreshold,
                                       method: method,
                                       headers: headers,
-                                      interceptor: interceptor)
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the providing `URLRequestConvertible`
     /// value and `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -457,20 +494,24 @@ public enum AF {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` instance to upload.
+    ///   - request:                 `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
     ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
     ///                              default.
-    ///   - request:                 `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                              default.
     ///
     /// - Returns:                   The created `UploadRequest`.
-    public static  func upload(multipartFormData: MultipartFormData,
-                               usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
-                               with request: URLRequestConvertible,
-                               interceptor: RequestInterceptor? = nil) -> UploadRequest {
+    public static func upload(multipartFormData: MultipartFormData,
+                              with request: URLRequestConvertible,
+                              usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
+                              interceptor: RequestInterceptor? = nil,
+                              fileManager: FileManager = .default) -> UploadRequest {
         return Session.default.upload(multipartFormData: multipartFormData,
-                                      usingThreshold: encodingMemoryThreshold,
                                       with: request,
-                                      interceptor: interceptor)
+                                      usingThreshold: encodingMemoryThreshold,
+                                      interceptor: interceptor,
+                                      fileManager: fileManager)
     }
 }

+ 8 - 3
Source/Request.swift

@@ -1330,6 +1330,10 @@ public class UploadRequest: DataRequest {
     /// The `UploadableConvertible` value used to produce the `Uploadable` value for this instance.
     public let upload: UploadableConvertible
 
+    /// `FileManager` used to perform cleanup tasks, including the removal of multipart form encoded payloads written
+    /// to disk.
+    public let fileManager: FileManager
+
     // MARK: Mutable State
 
     /// `Uploadable` value used by the instance.
@@ -1352,8 +1356,10 @@ public class UploadRequest: DataRequest {
          serializationQueue: DispatchQueue,
          eventMonitor: EventMonitor?,
          interceptor: RequestInterceptor?,
+         fileManager: FileManager,
          delegate: RequestDelegate) {
         self.upload = convertible
+        self.fileManager = fileManager
 
         super.init(id: id,
                    convertible: convertible,
@@ -1416,7 +1422,7 @@ public class UploadRequest: DataRequest {
     }
 
     public override func cleanup() {
-        super.cleanup()
+        defer { super.cleanup() }
 
         guard
             let uploadable = self.uploadable,
@@ -1424,8 +1430,7 @@ public class UploadRequest: DataRequest {
             shouldRemove
         else { return }
 
-        // TODO: Abstract file manager
-        try? FileManager.default.removeItem(at: url)
+        try? fileManager.removeItem(at: url)
     }
 }
 

+ 1 - 1
Source/ResponseSerialization.swift

@@ -197,7 +197,7 @@ extension DataRequest {
                     error: self.error
                 )
             }
-            
+
             let end = CFAbsoluteTimeGetCurrent()
             // End work that should be on the serialization queue.
 

+ 85 - 54
Source/Session.swift

@@ -42,7 +42,7 @@ open class Session {
     public let startRequestsImmediately: Bool
     /// `DispatchQueue` on which `URLRequest`s are created asynchronously. By default this queue uses `rootQueue` as its
     /// `target`, but a separate queue can be used if request creation is determined to be a bottleneck. Always profile
-    /// and test before introduing an additional queue.
+    /// and test before introducing an additional queue.
     public let requestQueue: DispatchQueue
     /// `DispatchQueue` passed to all `Request`s on which they perform their response serialization. By default this
     /// queue uses `rootQueue` as its `target` but a separate queue can be used if response serialization is determined
@@ -76,7 +76,7 @@ open class Session {
     ///   - session:                  Underlying `URLSession` for this instance.
     ///   - delegate:                 `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request`
     ///                               interaction.
-    ///   - rootQueue:                Root `DispatchQueue` for all internal callbacks and state updats. **MUST** be a
+    ///   - rootQueue:                Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a
     ///                               serial queue.
     ///   - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true`
     ///                               by default. If set to `false`, all `Request`s created must have `.resume()` called.
@@ -130,8 +130,8 @@ open class Session {
 
     /// Creates a `Session` from a `URLSessionConfiguration`.
     ///
-    /// - Note: This intializer lets Alamofire handle the creation of the underlying `URLSession` and its
-    ///         `delegateQueue`, and is the recommended intiailizer for most uses.
+    /// - Note: This initializer lets Alamofire handle the creation of the underlying `URLSession` and its
+    ///         `delegateQueue`, and is the recommended initializer for most uses.
     ///
     /// - Parameters:
     ///   - configuration:            `URLSessionConfiguration` to be used to create the underlying `URLSession`. Changes
@@ -139,7 +139,7 @@ open class Session {
     ///                               `URLSessionConfiguration.af.default` by default.
     ///   - delegate:                 `SessionDelegate` that handles `session`'s delegate callbacks as well as `Request`
     ///                               interaction. `SessionDelegate()` by default.
-    ///   - rootQueue:                Root `DispatchQueue` for all internal callbacks and state updats. **MUST** be a
+    ///   - rootQueue:                Root `DispatchQueue` for all internal callbacks and state updates. **MUST** be a
     ///                               serial queue. `DispatchQueue(label: "org.alamofire.session.rootQueue")` by default.
     ///   - startRequestsImmediately: Determines whether this instance will automatically start all `Request`s. `true`
     ///                               by default. If set to `false`, all `Request`s created must have `.resume()` called.
@@ -227,7 +227,7 @@ open class Session {
         }
     }
 
-    /// Creates a `DataRequest` from a `URLRequest` created using the passeed components and a `RequestInterceptor`.
+    /// Creates a `DataRequest` from a `URLRequest` created using the passed components and a `RequestInterceptor`.
     ///
     /// - Parameters:
     ///   - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
@@ -274,7 +274,7 @@ open class Session {
     /// - Parameters:
     ///   - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
     ///   - method:      `HTTPMethod` for the `URLRequest`. `.get` by default.
-    ///   - parameters:  Value conforming to `Encodable` to be encoded into the `URLRequest`. `nil` by default.
+    ///   - parameters:  `Encodable` value to be encoded into the `URLRequest`. `nil` by default.
     ///   - encoder:     `ParameterEncoder` to be used to encode the `parameters` value into the `URLRequest`.
     ///                  `URLEncodedFormParameterEncoder.default` by default.
     ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
@@ -474,16 +474,19 @@ open class Session {
     ///   - method:      `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
     ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
     /// - Returns:       The created `UploadRequest`.
     open func upload(_ data: Data,
                      to convertible: URLConvertible,
                      method: HTTPMethod = .post,
                      headers: HTTPHeaders? = nil,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let convertible = ParameterlessRequestConvertible(url: convertible, method: method, headers: headers)
 
-        return upload(data, with: convertible, interceptor: interceptor)
+        return upload(data, with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` for the given `Data` using the `URLRequestConvertible` value and `RequestInterceptor`.
@@ -492,12 +495,15 @@ open class Session {
     ///   - data:        The `Data` to upload.
     ///   - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
     /// - Returns:       The created `UploadRequest`.
     open func upload(_ data: Data,
                      with convertible: URLRequestConvertible,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return upload(.data(data), with: convertible, interceptor: interceptor)
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
+        return upload(.data(data), with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     // MARK: File
@@ -510,17 +516,20 @@ open class Session {
     ///   - convertible: `URLConvertible` value to be used as the `URLRequest`'s `URL`.
     ///   - method:      `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
-    ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - interceptor: `RequestInterceptor` value to be used by the returned `UploadRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
     /// - Returns:       The created `UploadRequest`.
     open func upload(_ fileURL: URL,
                      to convertible: URLConvertible,
                      method: HTTPMethod = .post,
                      headers: HTTPHeaders? = nil,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let convertible = ParameterlessRequestConvertible(url: convertible, method: method, headers: headers)
 
-        return upload(fileURL, with: convertible, interceptor: interceptor)
+        return upload(fileURL, with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` for the file at the given file `URL` using the `URLRequestConvertible` value and
@@ -530,12 +539,15 @@ open class Session {
     ///   - fileURL:     The `URL` of the file to upload.
     ///   - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
     /// - Returns:       The created `UploadRequest`.
     open func upload(_ fileURL: URL,
                      with convertible: URLRequestConvertible,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return upload(.file(fileURL, shouldRemove: false), with: convertible, interceptor: interceptor)
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
+        return upload(.file(fileURL, shouldRemove: false), with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     // MARK: InputStream
@@ -549,16 +561,19 @@ open class Session {
     ///   - method:      `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:     `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
     ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
     /// - Returns:       The created `UploadRequest`.
     open func upload(_ stream: InputStream,
                      to convertible: URLConvertible,
                      method: HTTPMethod = .post,
                      headers: HTTPHeaders? = nil,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let convertible = ParameterlessRequestConvertible(url: convertible, method: method, headers: headers)
 
-        return upload(stream, with: convertible, interceptor: interceptor)
+        return upload(stream, with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` from the provided `InputStream` using the `URLRequestConvertible` value and
@@ -568,12 +583,15 @@ open class Session {
     ///   - stream:      The `InputStream` that provides the data to upload.
     ///   - convertible: `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - interceptor: `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager: `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                  default.
     ///
     /// - Returns:       The created `UploadRequest`.
     open func upload(_ stream: InputStream,
                      with convertible: URLRequestConvertible,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
-        return upload(.stream(stream), with: convertible, interceptor: interceptor)
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
+        return upload(.stream(stream), with: convertible, interceptor: interceptor, fileManager: fileManager)
     }
 
     // MARK: MultipartFormData
@@ -581,7 +599,7 @@ open class Session {
     /// Creates an `UploadRequest` for the multipart form data built using a closure and sent using the provided
     /// `URLRequest` components and `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -596,38 +614,40 @@ open class Session {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` building closure.
-    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
-    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by default.
-    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
-    ///                              written to disk before being uploaded.
     ///   - convertible:             `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
+    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
+    ///                              default.
     ///   - method:                  `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:                 `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
+    ///                              written to disk before being uploaded. `.default` instance by default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     open func upload(multipartFormData: @escaping (MultipartFormData) -> Void,
-                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
-                     fileManager: FileManager = .default,
                      to url: URLConvertible,
+                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                      method: HTTPMethod = .post,
                      headers: HTTPHeaders? = nil,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let convertible = ParameterlessRequestConvertible(url: url, method: method, headers: headers)
 
         let formData = MultipartFormData(fileManager: fileManager)
         multipartFormData(formData)
 
         return upload(multipartFormData: formData,
-                      usingThreshold: encodingMemoryThreshold,
                       with: convertible,
-                      interceptor: interceptor)
+                      usingThreshold: encodingMemoryThreshold,
+                      interceptor: interceptor,
+                      fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` using a `MultipartFormData` building closure, the provided `URLRequestConvertible`
     /// value, and a `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -642,32 +662,34 @@ open class Session {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` building closure.
-    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
-    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by default.
-    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
-    ///                              written to disk before being uploaded.
     ///   - request:                 `URLRequestConvertible` value to be used to create the `URLRequest`.
+    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
+    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
+    ///                              default.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
+    ///                              written to disk before being uploaded. `.default` instance by default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     open func upload(multipartFormData: @escaping (MultipartFormData) -> Void,
-                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
-                     fileManager: FileManager = .default,
                      with request: URLRequestConvertible,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let formData = MultipartFormData(fileManager: fileManager)
         multipartFormData(formData)
 
         return upload(multipartFormData: formData,
-                      usingThreshold: encodingMemoryThreshold,
                       with: request,
-                      interceptor: interceptor)
+                      usingThreshold: encodingMemoryThreshold,
+                      interceptor: interceptor,
+                      fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the provided `URLRequest` components
     /// and `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -682,20 +704,24 @@ open class Session {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` instance to upload.
-    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
-    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by default.
     ///   - url:                     `URLConvertible` value to be used as the `URLRequest`'s `URL`.
+    ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
+    ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
+    ///                              default.
     ///   - method:                  `HTTPMethod` for the `URLRequest`. `.post` by default.
     ///   - headers:                 `HTTPHeaders` value to be added to the `URLRequest`. `nil` by default.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` to be used if the form data exceeds the memory threshold and is
+    ///                              written to disk before being uploaded. `.default` instance by default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     open func upload(multipartFormData: MultipartFormData,
-                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                      to url: URLConvertible,
+                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                      method: HTTPMethod = .post,
                      headers: HTTPHeaders? = nil,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let convertible = ParameterlessRequestConvertible(url: url, method: method, headers: headers)
 
         let multipartUpload = MultipartUpload(isInBackgroundSession: (session.configuration.identifier != nil),
@@ -703,13 +729,13 @@ open class Session {
                                               request: convertible,
                                               multipartFormData: multipartFormData)
 
-        return upload(multipartUpload, interceptor: interceptor)
+        return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager)
     }
 
     /// Creates an `UploadRequest` for the prebuilt `MultipartFormData` value using the providing `URLRequestConvertible`
     /// value and `RequestInterceptor`.
     ///
-    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
+    /// It is important to understand the memory implications of uploading `MultipartFormData`. If the cumulative
     /// 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
@@ -724,23 +750,26 @@ open class Session {
     ///
     /// - Parameters:
     ///   - multipartFormData:       `MultipartFormData` instance to upload.
+    ///   - request:                 `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - encodingMemoryThreshold: Byte threshold used to determine whether the form data is encoded into memory or
     ///                              onto disk before being uploaded. `MultipartFormData.encodingMemoryThreshold` by
     ///                              default.
-    ///   - request:                 `URLRequestConvertible` value to be used to create the `URLRequest`.
     ///   - interceptor:             `RequestInterceptor` value to be used by the returned `DataRequest`. `nil` by default.
+    ///   - fileManager:             `FileManager` instance to be used by the returned `UploadRequest`. `.default` instance by
+    ///                              default.
     ///
     /// - Returns:                   The created `UploadRequest`.
     open func upload(multipartFormData: MultipartFormData,
-                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
                      with request: URLRequestConvertible,
-                     interceptor: RequestInterceptor? = nil) -> UploadRequest {
+                     usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
+                     interceptor: RequestInterceptor? = nil,
+                     fileManager: FileManager = .default) -> UploadRequest {
         let multipartUpload = MultipartUpload(isInBackgroundSession: (session.configuration.identifier != nil),
                                               encodingMemoryThreshold: encodingMemoryThreshold,
                                               request: request,
                                               multipartFormData: multipartFormData)
 
-        return upload(multipartUpload, interceptor: interceptor)
+        return upload(multipartUpload, interceptor: interceptor, fileManager: fileManager)
     }
 
     // MARK: - Internal API
@@ -749,18 +778,20 @@ open class Session {
 
     func upload(_ uploadable: UploadRequest.Uploadable,
                 with convertible: URLRequestConvertible,
-                interceptor: RequestInterceptor?) -> UploadRequest {
+                interceptor: RequestInterceptor?,
+                fileManager: FileManager) -> UploadRequest {
         let uploadable = Upload(request: convertible, uploadable: uploadable)
 
-        return upload(uploadable, interceptor: interceptor)
+        return upload(uploadable, interceptor: interceptor, fileManager: fileManager)
     }
 
-    func upload(_ upload: UploadConvertible, interceptor: RequestInterceptor?) -> UploadRequest {
+    func upload(_ upload: UploadConvertible, interceptor: RequestInterceptor?, fileManager: FileManager) -> UploadRequest {
         let request = UploadRequest(convertible: upload,
                                     underlyingQueue: rootQueue,
                                     serializationQueue: serializationQueue,
                                     eventMonitor: eventMonitor,
                                     interceptor: interceptor,
+                                    fileManager: fileManager,
                                     delegate: self)
 
         perform(request)

+ 7 - 7
Tests/UploadTests.swift

@@ -465,8 +465,8 @@ class UploadMultipartFormDataTestCase: BaseTestCase {
                             multipartFormData.append(frenchData, withName: "french")
                             multipartFormData.append(japaneseData, withName: "japanese")
                         },
-                        usingThreshold: 0,
-                        to: urlString).response { resp in
+                        to: urlString,
+                        usingThreshold: 0).response { resp in
                             response = resp
                             expectation.fulfill()
                         }
@@ -498,8 +498,8 @@ class UploadMultipartFormDataTestCase: BaseTestCase {
                             multipartFormData.append(uploadData, withName: "upload_data")
                             formData = multipartFormData
                         },
-                        usingThreshold: 0,
-                        to: urlString).response { resp in
+                        to: urlString,
+                        usingThreshold: 0).response { resp in
                             response = resp
                             expectation.fulfill()
                         }
@@ -600,8 +600,8 @@ class UploadMultipartFormDataTestCase: BaseTestCase {
                 multipartFormData.append(loremData1, withName: "lorem1")
                 multipartFormData.append(loremData2, withName: "lorem2")
             },
-            usingThreshold: streamFromDisk ? 0 : 100_000_000,
-            to: urlString)
+            to: urlString,
+            usingThreshold: streamFromDisk ? 0 : 100_000_000)
             .uploadProgress { progress in
                 uploadProgressValues.append(progress.fractionCompleted)
             }
@@ -611,7 +611,7 @@ class UploadMultipartFormDataTestCase: BaseTestCase {
             .response { resp in
                 response = resp
                 expectation.fulfill()
-        }
+            }
 
         waitForExpectations(timeout: timeout, handler: nil)