Просмотр исходного кода

Added safety to URLStringConvertible, URLRequestConvertible and RequestAdapter.

All three protocols now throw in the event that an error occurs. This allows any errors that occur in the URL request creation process to bubble through to the response handlers rather than crash.
Christian Noon 9 лет назад
Родитель
Сommit
95a0ad51be

+ 21 - 0
Source/AFError.swift

@@ -27,6 +27,7 @@ import Foundation
 /// `AFError` is the error type returned by Alamofire. It encompasses a few different types of errors, each with
 /// their own associated reasons.
 ///
+/// - invalidURLString:            Returned when a URL string is missing or fails to create a valid `URL`.
 /// - parameterEncodingFailed:     Returned when a parameter encoding object throws an error during the encoding process.
 /// - multipartEncodingFailed:     Returned when some step in the multipart encoding process fails.
 /// - responseValidationFailed:    Returned when a `validate()` call fails.
@@ -124,6 +125,7 @@ public enum AFError: Error {
         case propertyListSerializationFailed(error: Error)
     }
 
+    case invalidURLString(urlString: URLStringConvertible)
     case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
     case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
     case responseValidationFailed(reason: ResponseValidationFailureReason)
@@ -133,6 +135,13 @@ public enum AFError: Error {
 // MARK: - Error Booleans
 
 extension AFError {
+    /// Returns whether the AFError is an invalid URL string error. When `true`, the `urlString` property will
+    /// contain the associated value.
+    public var isInvalidURLStringError: Bool {
+        if case .invalidURLString = self { return true }
+        return false
+    }
+
     /// Returns whether the AFError is a parameter encoding error. When `true`, the `underlyingError` property will
     /// contain the associated value.
     public var isParameterEncodingError: Bool {
@@ -175,6 +184,16 @@ extension AFError {
         }
     }
 
+    /// The `URLStringConvertible` associated with the error.
+    public var urlString: URLStringConvertible? {
+        switch self {
+        case .invalidURLString(let urlString):
+            return urlString
+        default:
+            return nil
+        }
+    }
+
     /// The `Error` returned by a system framework associated with a `.parameterEncodingFailed`,
     /// `.multipartEncodingFailed` or `.responseSerializationFailed` error.
     public var underlyingError: Error? {
@@ -321,6 +340,8 @@ extension AFError.ResponseSerializationFailureReason {
 extension AFError: LocalizedError {
     public var errorDescription: String? {
         switch self {
+        case .invalidURLString(let urlString):
+            return "URL string is not valid: \(urlString)"
         case .parameterEncodingFailed(let reason):
             return reason.localizedDescription
         case .multipartEncodingFailed(let reason):

+ 47 - 16
Source/Alamofire.swift

@@ -27,29 +27,45 @@ import Foundation
 /// Types adopting the `URLStringConvertible` protocol can be used to construct URL strings, which are then used to
 /// construct URL requests.
 public protocol URLStringConvertible {
-    /// A URL that conforms to RFC 2396.
+    /// A URL string that conforms to RFC 2396.
     ///
     /// Methods accepting a `URLStringConvertible` type parameter parse it according to RFCs 1738 and 1808.
     ///
     /// See https://tools.ietf.org/html/rfc2396
     /// See https://tools.ietf.org/html/rfc1738
     /// See https://tools.ietf.org/html/rfc1808
-    var urlString: String { get }
+    var urlString: String? { get }
+
+    /// Returns a URL string that conforms to RFC 2396 or throws if an `Error` was encoutered.
+    ///
+    /// Methods accepting a `URLStringConvertible` type parameter parse it according to RFCs 1738 and 1808.
+    ///
+    /// - throws: An `Error` if underlying url string is `nil`.
+    ///
+    /// - returns: A URL string.
+    func asURLString() throws -> String
+}
+
+extension URLStringConvertible {
+    public var urlString: String? { return try? asURLString() }
 }
 
 extension String: URLStringConvertible {
-    /// The URL string.
-    public var urlString: String { return self }
+    /// Returns a URL string that conforms to RFC 2396.
+    public func asURLString() throws -> String { return self }
 }
 
 extension URL: URLStringConvertible {
-    /// The URL string.
-    public var urlString: String { return absoluteString }
+    /// Returns a URL string that conforms to RFC 2396.
+    public func asURLString() throws -> String { return absoluteString }
 }
 
 extension URLComponents: URLStringConvertible {
-    /// The URL string.
-    public var urlString: String { return url!.urlString }
+    /// Returns a URL string that conforms to RFC 2396.
+    public func asURLString() throws -> String {
+        guard let urlString = url?.urlString else { throw AFError.invalidURLString(urlString: self) }
+        return urlString
+    }
 }
 
 // MARK: -
@@ -57,12 +73,25 @@ extension URLComponents: URLStringConvertible {
 /// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests.
 public protocol URLRequestConvertible {
     /// The URL request.
-    var urlRequest: URLRequest { get }
+    var urlRequest: URLRequest? { get }
+
+    /// Returns a URL request or throws if an `Error` was encountered.
+    ///
+    /// - throws: An `Error` if the underlying `URLRequest` is `nil`.
+    ///
+    /// - returns: A URL request.
+    func asURLRequest() throws -> URLRequest
 }
 
-extension URLRequest: URLRequestConvertible {
+extension URLRequestConvertible {
     /// The URL request.
-    public var urlRequest: URLRequest { return self }
+    public var urlRequest: URLRequest? { return try? asURLRequest() }
+}
+
+
+extension URLRequest: URLRequestConvertible {
+    /// Returns a URL request or throws if an `Error` was encountered.
+    public func asURLRequest() throws -> URLRequest { return self }
 }
 
 // MARK: -
@@ -75,10 +104,12 @@ extension URLRequest {
     /// - parameter headers:   The HTTP headers. `nil` by default.
     ///
     /// - returns: The new `URLRequest` instance.
-    public init(urlString: URLStringConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) {
-        self.init(url: URL(string: urlString.urlString)!)
+    public init(urlString: URLStringConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws {
+        let urlString = try urlString.asURLString()
+
+        guard let url = URL(string: urlString) else { throw AFError.invalidURLString(urlString: urlString) }
 
-        if let request = urlString as? URLRequest { self = request }
+        self.init(url: url)
 
         httpMethod = method.rawValue
 
@@ -89,9 +120,9 @@ extension URLRequest {
         }
     }
 
-    func adapt(using adapter: RequestAdapter?) -> URLRequest {
+    func adapt(using adapter: RequestAdapter?) throws -> URLRequest {
         guard let adapter = adapter else { return self }
-        return adapter.adapt(self)
+        return try adapter.adapt(self)
     }
 }
 

+ 6 - 6
Source/ParameterEncoding.swift

@@ -117,11 +117,11 @@ public struct URLEncoding: ParameterEncoding {
     /// - parameter urlRequest: The request to have parameters applied.
     /// - parameter parameters: The parameters to apply.
     ///
-    /// - throws: An `AFError.parameterEncodingFailed` error if encoding fails.
+    /// - throws: An `Error` if the encoding process encounters an error.
     ///
     /// - returns: The encoded request.
     public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
-        var urlRequest = urlRequest.urlRequest
+        var urlRequest = try urlRequest.asURLRequest()
 
         guard let parameters = parameters else { return urlRequest }
 
@@ -267,11 +267,11 @@ public struct JSONEncoding: ParameterEncoding {
     /// - parameter urlRequest: The request to have parameters applied.
     /// - parameter parameters: The parameters to apply.
     ///
-    /// - throws: An `AFError.parameterEncodingFailed` error if encoding fails.
+    /// - throws: An `Error` if the encoding process encounters an error.
     ///
     /// - returns: The encoded request.
     public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
-        var urlRequest = urlRequest.urlRequest
+        var urlRequest = try urlRequest.asURLRequest()
 
         guard let parameters = parameters else { return urlRequest }
 
@@ -338,11 +338,11 @@ public struct PropertyListEncoding: ParameterEncoding {
     /// - parameter urlRequest: The request to have parameters applied.
     /// - parameter parameters: The parameters to apply.
     ///
-    /// - throws: An `AFError.parameterEncodingFailed` error if encoding fails.
+    /// - throws: An `Error` if the encoding process encounters an error.
     ///
     /// - returns: The encoded request.
     public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
-        var urlRequest = urlRequest.urlRequest
+        var urlRequest = try urlRequest.asURLRequest()
 
         guard let parameters = parameters else { return urlRequest }
 

+ 33 - 17
Source/Request.swift

@@ -30,8 +30,10 @@ public protocol RequestAdapter {
     ///
     /// - parameter urlRequest: The URL request to adapt.
     ///
+    /// - throws: An `Error` if the adaptation encounters an error.
+    ///
     /// - returns: The adapted `URLRequest`.
-    func adapt(_ urlRequest: URLRequest) -> URLRequest
+    func adapt(_ urlRequest: URLRequest) throws -> URLRequest
 }
 
 // MARK: -
@@ -58,7 +60,7 @@ public protocol RequestRetrier {
 // MARK: -
 
 protocol TaskConvertible {
-    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) -> URLSessionTask
+    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
 }
 
 /// A dictionary of headers to apply to a `URLRequest`.
@@ -69,9 +71,16 @@ public typealias HTTPHeaders = [String: String]
 /// Responsible for sending a request and receiving the response and associated data from the server, as well as
 /// managing its underlying `URLSessionTask`.
 open class Request {
+
+    // MARK: Helper Types
+
     /// A closure executed when monitoring upload or download progress of a request.
     public typealias ProgressHandler = (Progress) -> Void
 
+    enum RequestType {
+        case data, download, upload, stream
+    }
+
     // MARK: Properties
 
     /// The delegate for the underlying task.
@@ -110,21 +119,28 @@ open class Request {
 
     // MARK: Lifecycle
 
-    init(session: URLSession, task: URLSessionTask, originalTask: TaskConvertible?) {
+    init(
+        session: URLSession,
+        requestType: RequestType,
+        task: URLSessionTask? = nil,
+        originalTask: TaskConvertible? = nil,
+        error: Error? = nil)
+    {
         self.session = session
         self.originalTask = originalTask
 
-        switch task {
-        case is URLSessionUploadTask:
+        switch (task, requestType) {
+        case (is URLSessionUploadTask, .upload), (nil, .upload):
             taskDelegate = UploadTaskDelegate(task: task)
-        case is URLSessionDataTask:
+        case (is URLSessionDataTask, .data), (nil, .data):
             taskDelegate = DataTaskDelegate(task: task)
-        case is URLSessionDownloadTask:
+        case (is URLSessionDownloadTask, .download), (nil, .download):
             taskDelegate = DownloadTaskDelegate(task: task)
         default:
             taskDelegate = TaskDelegate(task: task)
         }
 
+        delegate.error = error
         delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
     }
 
@@ -177,7 +193,7 @@ open class Request {
 
     /// Resumes the request.
     open func resume() {
-        guard let task = task else { return }
+        guard let task = task else { delegate.queue.isSuspended = false ; return }
 
         if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
 
@@ -337,8 +353,8 @@ open class DataRequest: Request {
     struct Requestable: TaskConvertible {
         let urlRequest: URLRequest
 
-        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) -> URLSessionTask {
-            let urlRequest = self.urlRequest.adapt(using: adapter)
+        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
+            let urlRequest = try self.urlRequest.adapt(using: adapter)
             return queue.syncResult { session.dataTask(with: urlRequest) }
         }
     }
@@ -424,12 +440,12 @@ open class DownloadRequest: Request {
         case request(URLRequest)
         case resumeData(Data)
 
-        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) -> URLSessionTask {
+        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
             let task: URLSessionTask
 
             switch self {
             case let .request(urlRequest):
-                let urlRequest = urlRequest.adapt(using: adapter)
+                let urlRequest = try urlRequest.adapt(using: adapter)
                 task = queue.syncResult { session.downloadTask(with: urlRequest) }
             case let .resumeData(resumeData):
                 task = queue.syncResult { session.downloadTask(withResumeData: resumeData) }
@@ -514,18 +530,18 @@ open class UploadRequest: DataRequest {
         case file(URL, URLRequest)
         case stream(InputStream, URLRequest)
 
-        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) -> URLSessionTask {
+        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
             let task: URLSessionTask
 
             switch self {
             case let .data(data, urlRequest):
-                let urlRequest = urlRequest.adapt(using: adapter)
+                let urlRequest = try urlRequest.adapt(using: adapter)
                 task = queue.syncResult { session.uploadTask(with: urlRequest, from: data) }
             case let .file(url, urlRequest):
-                let urlRequest = urlRequest.adapt(using: adapter)
+                let urlRequest = try urlRequest.adapt(using: adapter)
                 task = queue.syncResult { session.uploadTask(with: urlRequest, fromFile: url) }
             case let .stream(_, urlRequest):
-                let urlRequest = urlRequest.adapt(using: adapter)
+                let urlRequest = try urlRequest.adapt(using: adapter)
                 task = queue.syncResult { session.uploadTask(withStreamedRequest: urlRequest) }
             }
 
@@ -569,7 +585,7 @@ open class StreamRequest: Request {
         case stream(hostName: String, port: Int)
         case netService(NetService)
 
-        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) -> URLSessionTask {
+        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
             let task: URLSessionTask
 
             switch self {

+ 125 - 78
Source/SessionManager.swift

@@ -231,15 +231,12 @@ open class SessionManager {
         headers: HTTPHeaders? = nil)
         -> DataRequest
     {
-        let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
-
         do {
+            let urlRequest = try URLRequest(urlString: urlString, method: method, headers: headers)
             let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
             return request(resource: encodedURLRequest)
         } catch {
-            let request = self.request(resource: urlRequest)
-            request.delegate.error = error
-            return request
+            return DataRequest(session: session, requestType: .data, error: error)
         }
     }
 
@@ -251,17 +248,21 @@ open class SessionManager {
     ///
     /// - returns: The created `DataRequest`.
     open func request(resource urlRequest: URLRequestConvertible) -> DataRequest {
-        let originalRequest = urlRequest.urlRequest
-        let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
+        do {
+            let originalRequest = try urlRequest.asURLRequest()
+            let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
 
-        let task = originalTask.task(session: session, adapter: adapter, queue: queue)
-        let request = DataRequest(session: session, task: task, originalTask: originalTask)
+            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
+            let request = DataRequest(session: session, requestType: .data, task: task, originalTask: originalTask)
 
-        delegate[task] = request
+            delegate[task] = request
 
-        if startRequestsImmediately { request.resume() }
+            if startRequestsImmediately { request.resume() }
 
-        return request
+            return request
+        } catch {
+            return DataRequest(session: session, requestType: .data, error: error)
+        }
     }
 
     // MARK: - Download Request
@@ -294,15 +295,12 @@ open class SessionManager {
         to destination: DownloadRequest.DownloadFileDestination? = nil)
         -> DownloadRequest
     {
-        let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
-
         do {
+            let urlRequest = try URLRequest(urlString: urlString, method: method, headers: headers)
             let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
             return download(resource: encodedURLRequest, to: destination)
         } catch {
-            let request = download(resource: urlRequest, to: destination)
-            request.delegate.error = error
-            return request
+            return DownloadRequest(session: session, requestType: .download, error: error)
         }
     }
 
@@ -324,7 +322,12 @@ open class SessionManager {
         to destination: DownloadRequest.DownloadFileDestination? = nil)
         -> DownloadRequest
     {
-        return download(.request(urlRequest.urlRequest), to: destination)
+        do {
+            let urlRequest = try urlRequest.asURLRequest()
+            return download(.request(urlRequest), to: destination)
+        } catch {
+            return DownloadRequest(session: session, requestType: .download, error: error)
+        }
     }
 
     // MARK: Resume Data
@@ -359,16 +362,20 @@ open class SessionManager {
         to destination: DownloadRequest.DownloadFileDestination?)
         -> DownloadRequest
     {
-        let task = downloadable.task(session: session, adapter: adapter, queue: queue)
-        let request = DownloadRequest(session: session, task: task, originalTask: downloadable)
+        do {
+            let task = try downloadable.task(session: session, adapter: adapter, queue: queue)
+            let request = DownloadRequest(session: session, requestType: .download, task: task, originalTask: downloadable)
 
-        request.downloadDelegate.destination = destination
+            request.downloadDelegate.destination = destination
 
-        delegate[task] = request
+            delegate[task] = request
 
-        if startRequestsImmediately { request.resume() }
+            if startRequestsImmediately { request.resume() }
 
-        return request
+            return request
+        } catch {
+            return DownloadRequest(session: session, requestType: .download, error: error)
+        }
     }
 
     // MARK: - Upload Request
@@ -393,8 +400,12 @@ open class SessionManager {
         headers: HTTPHeaders? = nil)
         -> UploadRequest
     {
-        let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
-        return upload(fileURL, with: urlRequest)
+        do {
+            let urlRequest = try URLRequest(urlString: urlString, method: method, headers: headers)
+            return upload(fileURL, with: urlRequest)
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
     /// Creates a `UploadRequest` from the specified `urlRequest` for uploading the `file`.
@@ -407,7 +418,12 @@ open class SessionManager {
     /// - returns: The created `UploadRequest`.
     @discardableResult
     open func upload(_ fileURL: URL, with urlRequest: URLRequestConvertible) -> UploadRequest {
-        return upload(.file(fileURL, urlRequest.urlRequest))
+        do {
+            let urlRequest = try urlRequest.asURLRequest()
+            return upload(.file(fileURL, urlRequest))
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
     // MARK: Data
@@ -430,8 +446,12 @@ open class SessionManager {
         headers: HTTPHeaders? = nil)
         -> UploadRequest
     {
-        let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
-        return upload(data, with: urlRequest)
+        do {
+            let urlRequest = try URLRequest(urlString: urlString, method: method, headers: headers)
+            return upload(data, with: urlRequest)
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
     /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `data`.
@@ -444,7 +464,12 @@ open class SessionManager {
     /// - returns: The created `UploadRequest`.
     @discardableResult
     open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
-        return upload(.data(data, urlRequest.urlRequest))
+        do {
+            let urlRequest = try urlRequest.asURLRequest()
+            return upload(.data(data, urlRequest))
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
     // MARK: InputStream
@@ -467,8 +492,12 @@ open class SessionManager {
         headers: HTTPHeaders? = nil)
         -> UploadRequest
     {
-        let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
-        return upload(stream, with: urlRequest)
+        do {
+            let urlRequest = try URLRequest(urlString: urlString, method: method, headers: headers)
+            return upload(stream, with: urlRequest)
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
     /// Creates an `UploadRequest` from the specified `urlRequest` for uploading the `stream`.
@@ -481,7 +510,12 @@ open class SessionManager {
     /// - returns: The created `UploadRequest`.
     @discardableResult
     open func upload(_ stream: InputStream, with urlRequest: URLRequestConvertible) -> UploadRequest {
-        return upload(.stream(stream, urlRequest.urlRequest))
+        do {
+            let urlRequest = try urlRequest.asURLRequest()
+            return upload(.stream(stream, urlRequest))
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
     // MARK: MultipartFormData
@@ -519,14 +553,18 @@ open class SessionManager {
         headers: HTTPHeaders? = nil,
         encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
     {
-        let urlRequest = URLRequest(urlString: urlString, method: method, headers: headers)
-
-        return upload(
-            multipartFormData: multipartFormData,
-            usingThreshold: encodingMemoryThreshold,
-            with: urlRequest,
-            encodingCompletion: encodingCompletion
-        )
+        do {
+            let urlRequest = try URLRequest(urlString: urlString, method: method, headers: headers)
+
+            return upload(
+                multipartFormData: multipartFormData,
+                usingThreshold: encodingMemoryThreshold,
+                with: urlRequest,
+                encodingCompletion: encodingCompletion
+            )
+        } catch {
+            DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
+        }
     }
 
     /// Encodes `multipartFormData` using `encodingMemoryThreshold` and calls `encodingCompletion` with new
@@ -562,13 +600,13 @@ open class SessionManager {
             let formData = MultipartFormData()
             multipartFormData(formData)
 
-            var urlRequestWithContentType = urlRequest.urlRequest
-            urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
+            do {
+                var urlRequestWithContentType = try urlRequest.asURLRequest()
+                urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
 
-            let isBackgroundSession = self.session.configuration.identifier != nil
+                let isBackgroundSession = self.session.configuration.identifier != nil
 
-            if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
-                do {
+                if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
                     let data = try formData.encode()
 
                     let encodingResult = MultipartFormDataEncodingResult.success(
@@ -578,17 +616,13 @@ open class SessionManager {
                     )
 
                     DispatchQueue.main.async { encodingCompletion?(encodingResult) }
-                } catch {
-                    DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
-                }
-            } else {
-                let fileManager = FileManager.default
-                let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
-                let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
-                let fileName = UUID().uuidString
-                let fileURL = directoryURL.appendingPathComponent(fileName)
-
-                do {
+                } else {
+                    let fileManager = FileManager.default
+                    let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
+                    let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
+                    let fileName = UUID().uuidString
+                    let fileURL = directoryURL.appendingPathComponent(fileName)
+
                     var directoryError: Error?
 
                     // Create directory inside serial queue to ensure two threads don't do this in parallel
@@ -612,9 +646,9 @@ open class SessionManager {
                         )
                         encodingCompletion?(encodingResult)
                     }
-                } catch {
-                    DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
                 }
+            } catch {
+                DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
             }
         }
     }
@@ -622,18 +656,22 @@ open class SessionManager {
     // MARK: Private - Upload Implementation
 
     private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {
-        let task = uploadable.task(session: session, adapter: adapter, queue: queue)
-        let request = UploadRequest(session: session, task: task, originalTask: uploadable)
+        do {
+            let task = try uploadable.task(session: session, adapter: adapter, queue: queue)
+            let request = UploadRequest(session: session, requestType: .upload, task: task, originalTask: uploadable)
 
-        if case let .stream(inputStream, _) = uploadable {
-            request.delegate.taskNeedNewBodyStream = { _, _ in inputStream }
-        }
+            if case let .stream(inputStream, _) = uploadable {
+                request.delegate.taskNeedNewBodyStream = { _, _ in inputStream }
+            }
 
-        delegate[task] = request
+            delegate[task] = request
 
-        if startRequestsImmediately { request.resume() }
+            if startRequestsImmediately { request.resume() }
 
-        return request
+            return request
+        } catch {
+            return UploadRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
 #if !os(watchOS)
@@ -672,14 +710,18 @@ open class SessionManager {
     // MARK: Private - Stream Implementation
 
     private func stream(_ streamable: StreamRequest.Streamable) -> StreamRequest {
-        let task = streamable.task(session: session, adapter: adapter, queue: queue)
-        let request = StreamRequest(session: session, task: task, originalTask: streamable)
+        do {
+            let task = try streamable.task(session: session, adapter: adapter, queue: queue)
+            let request = StreamRequest(session: session, requestType: .upload, task: task, originalTask: streamable)
 
-        delegate[task] = request
+            delegate[task] = request
 
-        if startRequestsImmediately { request.resume() }
+            if startRequestsImmediately { request.resume() }
 
-        return request
+            return request
+        } catch {
+            return StreamRequest(session: session, requestType: .upload, error: error)
+        }
     }
 
 #endif
@@ -689,15 +731,20 @@ open class SessionManager {
     func retry(_ request: Request) -> Bool {
         guard let originalTask = request.originalTask else { return false }
 
-        let task = originalTask.task(session: session, adapter: adapter, queue: queue)
+        do {
+            let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
 
-        request.delegate.task = task // resets all task delegate data
+            request.delegate.task = task // resets all task delegate data
 
-        request.startTime = CFAbsoluteTimeGetCurrent()
-        request.endTime = nil
+            request.startTime = CFAbsoluteTimeGetCurrent()
+            request.endTime = nil
 
-        task.resume()
+            task.resume()
 
-        return true
+            return true
+        } catch {
+            request.delegate.error = error
+            return false
+        }
     }
 }

+ 4 - 4
Source/TaskDelegate.swift

@@ -46,7 +46,7 @@ open class TaskDelegate: NSObject {
 
     // MARK: Lifecycle
 
-    init(task: URLSessionTask) {
+    init(task: URLSessionTask?) {
         self.task = task
 
         self.queue = {
@@ -193,7 +193,7 @@ class DataTaskDelegate: TaskDelegate, URLSessionDataDelegate {
 
     // MARK: Lifecycle
 
-    override init(task: URLSessionTask) {
+    override init(task: URLSessionTask?) {
         mutableData = Data()
         progress = Progress(totalUnitCount: 0)
 
@@ -305,7 +305,7 @@ class DownloadTaskDelegate: TaskDelegate, URLSessionDownloadDelegate {
 
     // MARK: Lifecycle
 
-    override init(task: URLSessionTask) {
+    override init(task: URLSessionTask?) {
         progress = Progress(totalUnitCount: 0)
         super.init(task: task)
     }
@@ -411,7 +411,7 @@ class UploadTaskDelegate: DataTaskDelegate {
 
     // MARK: Lifecycle
 
-    override init(task: URLSessionTask) {
+    override init(task: URLSessionTask?) {
         uploadProgress = Progress(totalUnitCount: 0)
         super.init(task: task)
     }

+ 4 - 4
Tests/ParameterEncodingTests.swift

@@ -85,7 +85,7 @@ class URLParameterEncodingTestCase: ParameterEncodingTestCase {
     func testURLParameterEncodeOneStringKeyStringValueParameterAppendedToQuery() {
         do {
             // Given
-            var mutableURLRequest = self.urlRequest.urlRequest
+            var mutableURLRequest = self.urlRequest
             var urlComponents = URLComponents(url: mutableURLRequest.url!, resolvingAgainstBaseURL: false)!
             urlComponents.query = "baz=qux"
             mutableURLRequest.url = urlComponents.url
@@ -529,7 +529,7 @@ class URLParameterEncodingTestCase: ParameterEncodingTestCase {
     func testThatURLParameterEncodingEncodesGETParametersInURL() {
         do {
             // Given
-            var mutableURLRequest = self.urlRequest.urlRequest
+            var mutableURLRequest = self.urlRequest
             mutableURLRequest.httpMethod = HTTPMethod.get.rawValue
             let parameters = ["foo": 1, "bar": 2]
 
@@ -548,7 +548,7 @@ class URLParameterEncodingTestCase: ParameterEncodingTestCase {
     func testThatURLParameterEncodingEncodesPOSTParametersInHTTPBody() {
         do {
             // Given
-            var mutableURLRequest = self.urlRequest.urlRequest
+            var mutableURLRequest = self.urlRequest
             mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
             let parameters = ["foo": 1, "bar": 2]
 
@@ -572,7 +572,7 @@ class URLParameterEncodingTestCase: ParameterEncodingTestCase {
     func testThatURLEncodedInURLParameterEncodingEncodesPOSTParametersInURL() {
         do {
             // Given
-            var mutableURLRequest = self.urlRequest.urlRequest
+            var mutableURLRequest = self.urlRequest
             mutableURLRequest.httpMethod = HTTPMethod.post.rawValue
             let parameters = ["foo": 1, "bar": 2]
 

+ 7 - 7
Tests/SessionManagerTests.swift

@@ -264,7 +264,7 @@ class SessionManagerTestCase: BaseTestCase {
         manager = nil
 
         // Then
-        XCTAssertTrue(request?.task.state == .suspended, "request task state should be '.Suspended'")
+        XCTAssertTrue(request?.task?.state == .suspended, "request task state should be '.Suspended'")
         XCTAssertNil(manager, "manager should be nil")
     }
 
@@ -282,7 +282,7 @@ class SessionManagerTestCase: BaseTestCase {
         manager = nil
 
         // Then
-        let state = request.task.state
+        let state = request.task?.state
         XCTAssertTrue(state == .canceling || state == .completed, "state should be .Canceling or .Completed")
         XCTAssertNil(manager, "manager should be nil")
     }
@@ -301,7 +301,7 @@ class SessionManagerTestCase: BaseTestCase {
         let request = sessionManager.request("https://httpbin.org/get")
 
         // Then
-        XCTAssertEqual(request.task.originalRequest?.httpMethod, adapter.method.rawValue)
+        XCTAssertEqual(request.task?.originalRequest?.httpMethod, adapter.method.rawValue)
     }
 
     func testThatSessionManagerCallsRequestAdapterWhenCreatingDownloadRequest() {
@@ -317,7 +317,7 @@ class SessionManagerTestCase: BaseTestCase {
         let request = sessionManager.download("https://httpbin.org/get", to: destination)
 
         // Then
-        XCTAssertEqual(request.task.originalRequest?.httpMethod, adapter.method.rawValue)
+        XCTAssertEqual(request.task?.originalRequest?.httpMethod, adapter.method.rawValue)
     }
 
     func testThatSessionManagerCallsRequestAdapterWhenCreatingUploadRequestWithData() {
@@ -332,7 +332,7 @@ class SessionManagerTestCase: BaseTestCase {
         let request = sessionManager.upload("data".data(using: .utf8)!, to: "https://httpbin.org/post")
 
         // Then
-        XCTAssertEqual(request.task.originalRequest?.httpMethod, adapter.method.rawValue)
+        XCTAssertEqual(request.task?.originalRequest?.httpMethod, adapter.method.rawValue)
     }
 
     func testThatSessionManagerCallsRequestAdapterWhenCreatingUploadRequestWithFile() {
@@ -348,7 +348,7 @@ class SessionManagerTestCase: BaseTestCase {
         let request = sessionManager.upload(fileURL, to: "https://httpbin.org/post")
 
         // Then
-        XCTAssertEqual(request.task.originalRequest?.httpMethod, adapter.method.rawValue)
+        XCTAssertEqual(request.task?.originalRequest?.httpMethod, adapter.method.rawValue)
     }
 
     func testThatSessionManagerCallsRequestAdapterWhenCreatingUploadRequestWithInputStream() {
@@ -364,7 +364,7 @@ class SessionManagerTestCase: BaseTestCase {
         let request = sessionManager.upload(inputStream, to: "https://httpbin.org/post")
 
         // Then
-        XCTAssertEqual(request.task.originalRequest?.httpMethod, adapter.method.rawValue)
+        XCTAssertEqual(request.task?.originalRequest?.httpMethod, adapter.method.rawValue)
     }
 
     // MARK: Tests - Request Retrier

+ 1 - 1
Tests/URLProtocolTests.swift

@@ -79,7 +79,7 @@ class ProxyURLProtocol: URLProtocol {
 
     override func startLoading() {
         // rdar://26849668 - URLProtocol had some API's that didnt make the value type conversion
-        let urlRequest = (request.urlRequest as NSURLRequest).mutableCopy() as! NSMutableURLRequest
+        let urlRequest = (request.urlRequest! as NSURLRequest).mutableCopy() as! NSMutableURLRequest
         URLProtocol.setProperty(true, forKey: PropertyKeys.handledByForwarderURLProtocol, in: urlRequest)
         activeTask = session.dataTask(with: urlRequest as URLRequest)
         activeTask?.resume()

+ 34 - 16
Tests/ValidationTests.swift

@@ -337,19 +337,27 @@ class ContentTypeValidationTestCase: BaseTestCase {
         // Given
         class MockManager: SessionManager {
             override func request(resource urlRequest: URLRequestConvertible) -> DataRequest {
-                let originalRequest = urlRequest.urlRequest
-                let adaptedRequest = originalRequest.adapt(using: adapter)
+                do {
+                    let originalRequest = try urlRequest.asURLRequest()
+                    let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
 
-                let task: URLSessionDataTask = queue.syncResult { session.dataTask(with: adaptedRequest) }
+                    let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
 
-                let originalTask = DataRequest.Requestable(urlRequest: originalRequest)
-                let request = MockDataRequest(session: session, task: task, originalTask: originalTask)
+                    let request = MockDataRequest(
+                        session: session,
+                        requestType: .data,
+                        task: task,
+                        originalTask: originalTask
+                    )
 
-                delegate[request.delegate.task] = request
+                    delegate[task] = request
 
-                if startRequestsImmediately { request.resume() }
+                    if startRequestsImmediately { request.resume() }
 
-                return request
+                    return request
+                } catch {
+                    return DataRequest(session: session, requestType: .data, error: error)
+                }
             }
 
             override func download(
@@ -357,19 +365,29 @@ class ContentTypeValidationTestCase: BaseTestCase {
                 to destination: DownloadRequest.DownloadFileDestination? = nil)
                 -> DownloadRequest
             {
-                let originalRequest = urlRequest.urlRequest
-                let originalTask = DownloadRequest.Downloadable.request(originalRequest)
+                do {
+                    let originalRequest = try urlRequest.asURLRequest()
+                    let originalTask = DownloadRequest.Downloadable.request(originalRequest)
 
-                let task = originalTask.task(session: session, adapter: adapter, queue: queue)
-                let request = MockDownloadRequest(session: session, task: task, originalTask: originalTask)
+                    let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
 
-                request.downloadDelegate.destination = destination
+                    let request = MockDownloadRequest(
+                        session: session,
+                        requestType: .download,
+                        task: task,
+                        originalTask: originalTask
+                    )
 
-                delegate[request.delegate.task] = request
+                    request.downloadDelegate.destination = destination
 
-                if startRequestsImmediately { request.resume() }
+                    delegate[task] = request
 
-                return request
+                    if startRequestsImmediately { request.resume() }
+
+                    return request
+                } catch {
+                    return DownloadRequest(session: session, requestType: .download, error: error)
+                }
             }
         }