| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- //
- // ResponseSerialization.swift
- //
- // Copyright (c) 2014-2017 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- import Foundation
- // MARK: Protocols
- /// The type to which all data response serializers must conform in order to serialize a response.
- public protocol DataResponseSerializerProtocol {
- /// The type of serialized object to be created by this serializer.
- associatedtype SerializedObject
- /// The function used to serialize the response data in response handlers.
- func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> SerializedObject
- }
- /// The type to which all download response serializers must conform in order to serialize a response.
- public protocol DownloadResponseSerializerProtocol {
- /// The type of serialized object to be created by this `DownloadResponseSerializerType`.
- associatedtype SerializedObject
- /// The function used to serialize the downloaded data in response handlers.
- func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> SerializedObject
- }
- /// A serializer that can handle both data and download responses.
- public typealias ResponseSerializer = DataResponseSerializerProtocol & DownloadResponseSerializerProtocol
- /// By default, any serializer declared to conform to both types will get file serialization for free, as it just feeds
- /// the data read from disk into the data response serializer.
- public extension DownloadResponseSerializerProtocol where Self: DataResponseSerializerProtocol {
- public func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> SerializedObject {
- guard error == nil else { throw error! }
- guard let fileURL = fileURL else {
- throw AFError.responseSerializationFailed(reason: .inputFileNil)
- }
- let data: Data
- do {
- data = try Data(contentsOf: fileURL)
- } catch {
- throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))
- }
- do {
- return try serialize(request: request, response: response, data: data, error: error)
- } catch {
- throw error
- }
- }
- }
- // MARK: - AnyResponseSerializer
- /// A generic `ResponseSerializer` conforming type.
- public final class AnyResponseSerializer<Value>: ResponseSerializer {
- /// A closure which can be used to serialize data responses.
- public typealias DataSerializer = (_ request: URLRequest?, _ response: HTTPURLResponse?, _ data: Data?, _ error: Error?) throws -> Value
- /// A closure which can be used to serialize download reponses.
- public typealias DownloadSerializer = (_ request: URLRequest?, _ response: HTTPURLResponse?, _ fileURL: URL?, _ error: Error?) throws -> Value
- let dataSerializer: DataSerializer
- let downloadSerializer: DownloadSerializer?
- /// Initialze the instance with both a `DataSerializer` closure and a `DownloadSerializer` closure.
- ///
- /// - Parameters:
- /// - dataSerializer: A `DataSerializer` closure.
- /// - downloadSerializer: A `DownloadSerializer` closure.
- public init(dataSerializer: @escaping DataSerializer, downloadSerializer: @escaping DownloadSerializer) {
- self.dataSerializer = dataSerializer
- self.downloadSerializer = downloadSerializer
- }
- /// Initialze the instance with a `DataSerializer` closure. Download serialization will fallback to a default
- /// implementation.
- ///
- /// - Parameters:
- /// - dataSerializer: A `DataSerializer` closure.
- public init(dataSerializer: @escaping DataSerializer) {
- self.dataSerializer = dataSerializer
- self.downloadSerializer = nil
- }
- public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Value {
- return try dataSerializer(request, response, data, error)
- }
- public func serializeDownload(request: URLRequest?, response: HTTPURLResponse?, fileURL: URL?, error: Error?) throws -> Value {
- return try downloadSerializer?(request, response, fileURL, error) ?? { (request, response, fileURL, error) in
- guard error == nil else { throw error! }
- guard let fileURL = fileURL else {
- throw AFError.responseSerializationFailed(reason: .inputFileNil)
- }
- let data: Data
- do {
- data = try Data(contentsOf: fileURL)
- } catch {
- throw AFError.responseSerializationFailed(reason: .inputFileReadFailed(at: fileURL))
- }
- do {
- return try serialize(request: request, response: response, data: data, error: error)
- } catch {
- throw error
- }
- }(request, response, fileURL, error)
- }
- }
- // MARK: - Timeline
- extension Request {
- var timeline: Timeline {
- let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent()
- let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
- let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
- return Timeline(
- requestStartTime: requestStartTime,
- initialResponseTime: initialResponseTime,
- requestCompletedTime: requestCompletedTime,
- serializationCompletedTime: CFAbsoluteTimeGetCurrent()
- )
- }
- }
- // MARK: - Default
- extension DataRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - completionHandler: The code to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func response(queue: DispatchQueue? = nil, completionHandler: @escaping (DefaultDataResponse) -> Void) -> Self {
- delegate.queue.addOperation {
- (queue ?? DispatchQueue.main).async {
- var dataResponse = DefaultDataResponse(
- request: self.request,
- response: self.response,
- data: self.delegate.data,
- error: self.delegate.error,
- timeline: self.timeline
- )
- dataResponse.add(self.delegate.metrics)
- completionHandler(dataResponse)
- }
- }
- return self
- }
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - responseSerializer: The response serializer responsible for serializing the request, response, and data.
- /// - completionHandler: The code to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func response<T: DataResponseSerializerProtocol>(
- queue: DispatchQueue? = nil,
- responseSerializer: T,
- completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
- -> Self
- {
- delegate.queue.addOperation {
- let result = Result { try responseSerializer.serialize(request: self.request,
- response: self.response,
- data: self.delegate.data,
- error: self.delegate.error) }
- var dataResponse = DataResponse<T.SerializedObject>(
- request: self.request,
- response: self.response,
- data: self.delegate.data,
- result: result,
- timeline: self.timeline
- )
- dataResponse.add(self.delegate.metrics)
- (queue ?? .main).async { completionHandler(dataResponse) }
- }
- return self
- }
- }
- extension DownloadRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - completionHandler: The code to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func response(
- queue: DispatchQueue? = nil,
- completionHandler: @escaping (DefaultDownloadResponse) -> Void)
- -> Self
- {
- delegate.queue.addOperation {
- (queue ?? .main).async {
- var downloadResponse = DefaultDownloadResponse(
- request: self.request,
- response: self.response,
- temporaryURL: self.downloadDelegate.temporaryURL,
- destinationURL: self.downloadDelegate.destinationURL,
- resumeData: self.downloadDelegate.resumeData,
- error: self.downloadDelegate.error,
- timeline: self.timeline
- )
- downloadResponse.add(self.delegate.metrics)
- completionHandler(downloadResponse)
- }
- }
- return self
- }
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - responseSerializer: The response serializer responsible for serializing the request, response, and data
- /// contained in the destination url.
- /// - completionHandler: The code to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func response<T: DownloadResponseSerializerProtocol>(
- queue: DispatchQueue? = nil,
- responseSerializer: T,
- completionHandler: @escaping (DownloadResponse<T.SerializedObject>) -> Void)
- -> Self
- {
- delegate.queue.addOperation {
- let result = Result { try responseSerializer.serializeDownload(request: self.request,
- response: self.response,
- fileURL: self.downloadDelegate.fileURL,
- error: self.downloadDelegate.error) }
- var downloadResponse = DownloadResponse<T.SerializedObject>(
- request: self.request,
- response: self.response,
- temporaryURL: self.downloadDelegate.temporaryURL,
- destinationURL: self.downloadDelegate.destinationURL,
- resumeData: self.downloadDelegate.resumeData,
- result: result,
- timeline: self.timeline
- )
- downloadResponse.add(self.delegate.metrics)
- (queue ?? .main).async { completionHandler(downloadResponse) }
- }
- return self
- }
- }
- // MARK: - Data
- extension DataRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - completionHandler: The code to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseData(
- queue: DispatchQueue? = nil,
- completionHandler: @escaping (DataResponse<Data>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: DataResponseSerializer(),
- completionHandler: completionHandler
- )
- }
- }
- /// A `ResponseSerializer` that performs minimal reponse checking and returns any response data as-is. By default, a
- /// request returning `nil` or no data is considered an error. However, if the response is has a status code valid for
- /// empty responses (`204`, `205`), then an empty `Data` value is returned.
- public final class DataResponseSerializer: ResponseSerializer {
- public init() { }
- public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Data {
- guard error == nil else { throw error! }
- if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return Data() }
- guard let validData = data else {
- throw AFError.responseSerializationFailed(reason: .inputDataNil)
- }
- return validData
- }
- }
- extension DownloadRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - completionHandler: The code to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseData(
- queue: DispatchQueue? = nil,
- completionHandler: @escaping (DownloadResponse<Data>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: DataResponseSerializer(),
- completionHandler: completionHandler
- )
- }
- }
- // MARK: - String
- /// A `ResponseSerializer` that decodes the response data as a `String`. By default, a request returning `nil` or no
- /// data is considered an error. However, if the response is has a status code valid for empty responses (`204`, `205`),
- /// then an empty `String` is returned.
- public final class StringResponseSerializer: ResponseSerializer {
- let encoding: String.Encoding?
- /// Creates an instance with the given `String.Encoding`.
- ///
- /// - Parameter encoding: A string encoding. Defaults to `nil`, in which case the encoding will be determined from
- /// the server response, falling back to the default HTTP character set, `ISO-8859-1`.
- public init(encoding: String.Encoding? = nil) {
- self.encoding = encoding
- }
- public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> String {
- guard error == nil else { throw error! }
- if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return "" }
- guard let validData = data else {
- throw AFError.responseSerializationFailed(reason: .inputDataNil)
- }
- var convertedEncoding = encoding
- if let encodingName = response?.textEncodingName as CFString!, convertedEncoding == nil {
- let ianaCharSet = CFStringConvertIANACharSetNameToEncoding(encodingName)
- let nsStringEncoding = CFStringConvertEncodingToNSStringEncoding(ianaCharSet)
- convertedEncoding = String.Encoding(rawValue: nsStringEncoding)
- }
- let actualEncoding = convertedEncoding ?? .isoLatin1
- guard let string = String(data: validData, encoding: actualEncoding) else {
- throw AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding))
- }
- return string
- }
- }
- extension DataRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined from
- /// the server response, falling back to the default HTTP character set, `ISO-8859-1`.
- /// - completionHandler: A closure to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseString(
- queue: DispatchQueue? = nil,
- encoding: String.Encoding? = nil,
- completionHandler: @escaping (DataResponse<String>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: StringResponseSerializer(encoding: encoding),
- completionHandler: completionHandler
- )
- }
- }
- extension DownloadRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - encoding: The string encoding. Defaults to `nil`, in which case the encoding will be determined from
- /// the server response, falling back to the default HTTP character set, `ISO-8859-1`.
- /// - completionHandler: A closure to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseString(
- queue: DispatchQueue? = nil,
- encoding: String.Encoding? = nil,
- completionHandler: @escaping (DownloadResponse<String>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: StringResponseSerializer(encoding: encoding),
- completionHandler: completionHandler
- )
- }
- }
- // MARK: - JSON
- /// A `ResponseSerializer` that decodes the response data using `JSONSerialization`. By default, a request returning
- /// `nil` or no data is considered an error. However, if the response is has a status code valid for empty responses
- /// (`204`, `205`), then an `NSNull` value is returned.
- public final class JSONResponseSerializer: ResponseSerializer {
- let options: JSONSerialization.ReadingOptions
- /// Creates an instance with the given `JSONSerilization.ReadingOptions`.
- ///
- /// - Parameter options: The options to use. Defaults to `.allowFragments`.
- public init(options: JSONSerialization.ReadingOptions = .allowFragments) {
- self.options = options
- }
- public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Any {
- guard error == nil else { throw error! }
- guard let validData = data, validData.count > 0 else {
- if let response = response, emptyDataStatusCodes.contains(response.statusCode) {
- return NSNull()
- }
- throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
- }
- do {
- return try JSONSerialization.jsonObject(with: validData, options: options)
- } catch {
- throw AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))
- }
- }
- }
- extension DataRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - options: The JSON serialization reading options. Defaults to `.allowFragments`.
- /// - completionHandler: A closure to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseJSON(
- queue: DispatchQueue? = nil,
- options: JSONSerialization.ReadingOptions = .allowFragments,
- completionHandler: @escaping (DataResponse<Any>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: JSONResponseSerializer(options: options),
- completionHandler: completionHandler
- )
- }
- }
- extension DownloadRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - options: The JSON serialization reading options. Defaults to `.allowFragments`.
- /// - completionHandler: A closure to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseJSON(
- queue: DispatchQueue? = nil,
- options: JSONSerialization.ReadingOptions = .allowFragments,
- completionHandler: @escaping (DownloadResponse<Any>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: JSONResponseSerializer(options: options),
- completionHandler: completionHandler
- )
- }
- }
- // MARK: - Empty
- /// A type representing an empty response. Use `Empty.response` to get the instance.
- public struct Empty: Decodable {
- public static let response = Empty()
- }
- // MARK: - JSON Decodable
- /// A `ResponseSerializer` that decodes the response data as a generic value using a `JSONDecoder`. By default, a
- /// request returning `nil` or no data is considered an error. However, if the response is has a status code valid for
- /// empty responses (`204`, `205`), then the `Empty.response` value is returned.
- public final class JSONDecodableResponseSerializer<T: Decodable>: ResponseSerializer {
- let decoder: JSONDecoder
- /// Creates an instance with the given `JSONDecoder` instance.
- ///
- /// - Parameter decoder: A decoder. Defaults to a `JSONDecoder` with default settings.
- public init(decoder: JSONDecoder = JSONDecoder()) {
- self.decoder = decoder
- }
- public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T {
- guard error == nil else { throw error! }
- guard let validData = data, validData.count > 0 else {
- if let response = response, emptyDataStatusCodes.contains(response.statusCode) {
- guard let emptyResponse = Empty.response as? T else {
- throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)"))
- }
- return emptyResponse
- }
- throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
- }
- do {
- return try decoder.decode(T.self, from: validData)
- } catch {
- throw error
- }
- }
- }
- extension DataRequest {
- /// Adds a handler to be called once the request has finished.
- ///
- /// - Parameters:
- /// - queue: The queue on which the completion handler is dispatched. Defaults to `nil`, which means
- /// the handler is called on `.main`.
- /// - decoder: The decoder to use to decode the response. Defaults to a `JSONDecoder` with default
- /// settings.
- /// - completionHandler: A closure to be executed once the request has finished.
- /// - Returns: The request.
- @discardableResult
- public func responseJSONDecodable<T: Decodable>(
- queue: DispatchQueue? = nil,
- decoder: JSONDecoder = JSONDecoder(),
- completionHandler: @escaping (DataResponse<T>) -> Void)
- -> Self
- {
- return response(
- queue: queue,
- responseSerializer: JSONDecodableResponseSerializer(decoder: decoder),
- completionHandler: completionHandler
- )
- }
- }
- /// A set of HTTP response status code that do not contain response data.
- private let emptyDataStatusCodes: Set<Int> = [204, 205]
|