#Alamofire源碼解讀html
AFNetworking的做者Matt Thompson 提出了一個新的相似AFNetworking的網絡基礎庫,而且專門使用最新的Swift語言寫的,名爲 Alamofire.git
對於使用OC的開發者來講必定十分熟悉AFNetworking這個框架,由於如今咱們的app只要是有關於網絡訪問的部分大部分都會經過這個框架來進行網絡的訪問。github
而Alamofire 是 Swift 語言的 HTTP 網絡開發工具包,功能強大,支持各類 HTTP Method、JSON、文件上傳、文件下載和多種認證方法。json
因此當咱們採用Swift進行開發的時候,咱們就有了更多的選擇,是採用OC的AFNetworking也好,仍是採用Swift的Alamofire也行...swift
相信做爲開發者,你們都在其餘地方或多或少的讀過關於AFNetworking的解析,有的甚至也本身作過AFNetworking的解析, 那麼爲何還要再作Alamofire的解析呢? 緣由有如下幾點,api
OK 進入正題. 首先做爲一個新的網絡庫,Alamofire同時做爲AFnetworking的Swift版本,甚至是做爲同一個做者的開源庫,不管從哪一個角度來講,Alamofire都有不少和AFnetworking類似的地方... 如何對比,咱們首先把Alamofire加上項目中看看 網上很容易找到方法,直接加入項目,或者是採用CocoaPods進行安裝. 這裏我也簡單的展現一下數組
第一種方式 咱們下載這個庫文件 github.com/Alamofire/A… 咱們建立一個項目並把alamofire拖到整個項目根目錄中 xcode
打開工程後選擇Alamofire.xcodeproj 拖入工程中 選擇本身的工程.xcodeproj->Build Phases->Link Binary With Libraries添加Alamofire.framework 以後可能須要在info.plist中添加(若是你的網路請求地址是http的話)在須要的網絡請求的地方相似這樣使用便可 緩存
這樣手動導入框架就已經完成了 固然做爲開發者,大部分都會使用CocoaPods的方式,這裏我就不囉嗦了,你們自行百度使用CocoaPods的方式便可安全
OK,咱們這裏先不看使用的方式,咱們開始解讀源碼,咱們打開Alamofire咱們能夠看到全部的文件
若是使用的是CocoaPods的話也能夠看到相同的 總共有17個文件我會按照網絡請求的整個過程來逐一解析
OK,做爲一個網絡庫,第一件咱們會幹的是什麼事情呢?我會創造一個請求, 這裏麪包括,請求的地址,參數,策略等等,而後由一個會話管理髮起請求.並接受相應, 因此咱們就先看這個ParameterEncoding 爲何呢?咱們都知道,咱們發起一個請求,會有這樣的一些設置,設置請求方式:get\post\put\delete...而後咱們會設置請求協議:http\ftp...,接着設置域名,設置主機地址,路徑,端口號,參數.... 這樣纔可以組成一次完整的請求.不然咱們連請求地址都不對,還談什麼請求呢?
ParameterEncoding部分
/// HTTP method definitions.
///
/// See https://tools.ietf.org/html/rfc7231#section-4.3
public enum HTTPMethod: String {
case options = "OPTIONS"
case get = "GET"
case head = "HEAD"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case trace = "TRACE"
case connect = "CONNECT"
}
複製代碼
咱們在ParameterEncoding中能夠看到,這是一個http請求類型的枚舉. 在Swift中,枚舉跟OC有了很大的區別,使用的是case的方式.
public protocol ParameterEncoding {
/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `AFError.parameterEncodingFailed` error if encoding fails.
///
/// - returns: The encoded request.
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
複製代碼
關於ParameterEncoding協議 這個協議中只有一個函數,該函數須要兩個參數:
urlRequest 該參數須要實現URLRequestConvertible協議,實現URLRequestConvertible協議的對象可以轉換成URLRequest
parameters 參數,其類型爲Parameters,也就是字典:public typealias Parameters = [String: Any]
複製代碼
該函數返回值類型爲URLRequest。經過觀察這個函數,咱們就明白了這個函數的目的就是把參數綁定到urlRequest之中,至於返回的urlRequest是否是以前的urlRequest,這個不必定,另外一個比較重要的是該函數會拋出異常,所以在本篇後邊的解讀中會說明該異常的來源。
咱們已經知道了URLEncoding就是和URL相關的編碼。當把參數編碼到httpBody中這種狀況是不受限制的,而直接編碼到URL中就會受限制,只有當HTTPMethod爲GET, HEAD and DELETE時才直接編碼到URL中。
public enum Destination {
case methodDependent, queryString, httpBody
}
複製代碼
methodDependent 根據HTTPMethod自動判斷採起哪一種編碼方式
queryString 拼接到URL中
httpBody 拼接到httpBody中
複製代碼
// MARK: Properties
/// Returns a default `URLEncoding` instance.
public static var `default`: URLEncoding { return URLEncoding() }
/// Returns a `URLEncoding` instance with a `.methodDependent` destination.
public static var methodDependent: URLEncoding { return URLEncoding() }
/// Returns a `URLEncoding` instance with a `.queryString` destination.
public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) }
/// Returns a `URLEncoding` instance with an `.httpBody` destination.
public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }
/// The destination defining where the encoded query string is to be applied to the URL request.
public let destination: Destination
// MARK: Initialization
/// Creates a `URLEncoding` instance using the specified destination.
///
/// - parameter destination: The destination defining where the encoded query string is to be applied.
///
/// - returns: The new `URLEncoding` instance.
public init(destination: Destination = .methodDependent) {
self.destination = destination
複製代碼
從這個地方咱們能夠看出Alamofire的URLEncoding提供了默認的初始化選擇的Destination是methodDependent,除了default這個單利外,又增長了其餘的三個
這裏採用了類屬性類建立單例
public static var `default`: URLEncoding { return URLEncoding() }
複製代碼
接下來是encode的方法
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()//獲取UrlRequest
guard let parameters = parameters else { return urlRequest }//若是參數爲nil就直接返回urlRequest
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {// 參數編碼到url
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
//分解url
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
//把原有的url中的query百分比編碼後在拼接上編碼後的參數
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {// 參數編碼到httpBody
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {// 設置Content-Type
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}
return urlRequest
}
複製代碼
因此以上是url的組成,只有完整組成了url而且定義了請求方式,請求類型....纔可以正確的獲取到請求和響應.
上面的是參數的組成,可是在這以前參數是如何對應,轉碼而且轉化成字符串的呢? 咱們能夠看到上面有用到一個query的方法,咱們學習一下 private func query(_ parameters: [String: Any]) -> String { var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
複製代碼
看到了咱們很熟悉的拼接字符串 上邊函數的總體思路是:
寫一個數組,這個數組中存放的是元組數據,元組中存放的是key和字符串類型的value
遍歷參數,對參數作進一步的處理,而後拼接到數組中
進一步處理數組內部的元組數據,把元組內部的數據用=號拼接,而後用符號&把數組拼接成字符串
複製代碼
這裏面用到了
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: "\(key)[]", value: value)
}
} else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool {
components.append((escape(key), escape((bool ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
return components
}
複製代碼
咱們能夠看到value是Any的類型,因此在這個方法裏面是作類型的轉換同時作轉碼,咱們再看看轉碼方法
public func escape(_ string: String) -> String {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowedCharacterSet = CharacterSet.urlQueryAllowed
allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
var escaped = ""
//==========================================================================================================
//
// Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
// hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
// longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
// info, please refer to:
//
// - https://github.com/Alamofire/Alamofire/issues/206
//
//==========================================================================================================
if #available(iOS 8.3, *) {
escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
} else {
let batchSize = 50
var index = string.startIndex
while index != string.endIndex {
let startIndex = index
let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
let range = startIndex..<endIndex
let substring = string.substring(with: range)
escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring
index = endIndex
}
}
return escaped
}
複製代碼
不一樣於網上流行的轉碼方式,可是大體上的思路仍然是同樣的,並且我能夠跟你說,這個方法很優秀,網上流傳的轉碼的方法有的時候仍然有侷限性,可是這個不會,最起碼我至今沒有發現,因此不管是我用不用Alamofire,我都會把這個轉碼的方式給單獨拿出來,在我須要的時候使用,屢試不爽...
到這裏,URLEncoding的所有內容就分析完畢了,咱們把不一樣的功能劃分紅不一樣的函數,這種作法最大的好處就是咱們可使用單獨的函數作獨立的事情。我徹底可使用escape這個函數轉義任何字符串。
繼續向下看則是JSONEncoding JSONEncoding的主要做用是把參數以JSON的形式編碼到request之中,固然是經過request的httpBody進行賦值的。JSONEncoding提供了兩種處理函數,一種是對普通的字典參數進行編碼,另外一種是對JSONObject進行編碼,處理這兩種狀況的函數基本上是相同的
public struct JSONEncoding: ParameterEncoding {
// MARK: Properties
/// Returns a `JSONEncoding` instance with default writing options.
public static var `default`: JSONEncoding { return JSONEncoding() }
/// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options.
public static var prettyPrinted: JSONEncoding { return JSONEncoding(options: .prettyPrinted) }
/// The options for writing the parameters as JSON data.
public let options: JSONSerialization.WritingOptions
// MARK: Initialization
/// Creates a `JSONEncoding` instance using the specified options.
///
/// - parameter options: The options for writing the parameters as JSON data.
///
/// - returns: The new `JSONEncoding` instance.
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
複製代碼
這裏邊值得注意的是JSONSerialization.WritingOptions,也就是JSON序列化的寫入方式。WritingOptions是一個結構體,系統提供了一個選項:prettyPrinted,意思是更好的打印效果
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let jsonObject = jsonObject else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
複製代碼
至於encode方法,第一個函數實現了ParameterEncoding協議,第二個參數做爲擴展,函數中最核心的內容是把參數變成Data類型,而後給httpBody賦值,須要注意的是異常處理。
至於接下來的PropertyListEncoding,基本上和JSONEncoding差很少是同樣的,這裏就不介紹了
Request部分 有了參數和地址,那固然的就是發起請求,天然而然的就到了request部分
enum RequestTask {
case data(TaskConvertible?, URLSessionTask?)
case download(TaskConvertible?, URLSessionTask?)
case upload(TaskConvertible?, URLSessionTask?)
case stream(TaskConvertible?, URLSessionTask?)
}
複製代碼
這是一個task的枚舉,分別表示各類請求的方式,咱們能夠熟悉的看到系統的類URLSessionTask,因此說Alamofire也是基於URLSession來寫的網絡框架
/// The underlying task.
open var task: URLSessionTask? { return delegate.task }
/// The session belonging to the underlying task.
open let session: URLSession
/// The request sent or to be sent to the server.
open var request: URLRequest? { return task?.originalRequest }
/// The response received from the server, if any.
open var response: HTTPURLResponse? { return task?.response as? HTTPURLResponse }
/// The number of times the request has been retried.
open internal(set) var retryCount: UInt = 0
let originalTask: TaskConvertible?
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?
var validations: [() -> Void] = []
private var taskDelegate: TaskDelegate
private var taskDelegateLock = NSLock()
// MARK: Lifecycle
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
self.session = session
switch requestTask {
case .data(let originalTask, let task):
taskDelegate = DataTaskDelegate(task: task)
self.originalTask = originalTask
case .download(let originalTask, let task):
taskDelegate = DownloadTaskDelegate(task: task)
self.originalTask = originalTask
case .upload(let originalTask, let task):
taskDelegate = UploadTaskDelegate(task: task)
self.originalTask = originalTask
case .stream(let originalTask, let task):
taskDelegate = TaskDelegate(task: task)
self.originalTask = originalTask
}
delegate.error = error
delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
複製代碼
這裏是request的初始化和一些設置的默認屬性... 這部分是auth
// MARK: Authentication
/// Associates an HTTP Basic credential with the request.
///
/// - parameter user: The user.
/// - parameter password: The password.
/// - parameter persistence: The URL credential persistence. `.ForSession` by default.
///
/// - returns: The request.
@discardableResult
open func authenticate(
user: String,
password: String,
persistence: URLCredential.Persistence = .forSession)
-> Self
{
let credential = URLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
/// Associates a specified credential with the request.
///
/// - parameter credential: The credential.
///
/// - returns: The request.
@discardableResult
open func authenticate(usingCredential credential: URLCredential) -> Self {
delegate.credential = credential
return self
}
/// Returns a base64 encoded basic authentication credential as an authorization header tuple.
///
/// - parameter user: The user.
/// - parameter password: The password.
///
/// - returns: A tuple with Authorization header and credential value if encoding succeeds, `nil` otherwise.
open static func authorizationHeader(user: String, password: String) -> (key: String, value: String)? {
guard let data = "\(user):\(password)".data(using: .utf8) else { return nil }
let credential = data.base64EncodedString(options: [])
return (key: "Authorization", value: "Basic \(credential)")
}
複製代碼
接下來並無什麼特殊的方式 相似於NSURLSession 分別有對task的恢復,暫停和取消
/// Resumes the request.
open func resume() {
guard let task = task else { delegate.queue.isSuspended = false ; return }
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NotificationCenter.default.post(
name: Notification.Name.Task.DidResume,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
/// Suspends the request.
open func suspend() {
guard let task = task else { return }
task.suspend()
NotificationCenter.default.post(
name: Notification.Name.Task.DidSuspend,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
/// Cancels the request.
open func cancel() {
guard let task = task else { return }
task.cancel()
NotificationCenter.default.post(
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
複製代碼
對應的實現會發送對應的通知而已
接下來是DataReuqest,做爲請求的一種方式,繼承自request,是用來管理一個潛在的URLSessionDataTask.
// MARK: Helper Types
struct Requestable: TaskConvertible {
let urlRequest: URLRequest
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
do {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.sync { session.dataTask(with: urlRequest) }
} catch {
throw AdaptError(error: error)
}
}
}
// MARK: Properties
/// The request sent or to be sent to the server.
open override var request: URLRequest? {
if let request = super.request { return request }
if let requestable = originalTask as? Requestable { return requestable.urlRequest }
return nil
}
/// The progress of fetching the response data from the server for the request.
open var progress: Progress { return dataDelegate.progress }
var dataDelegate: DataTaskDelegate { return delegate as! DataTaskDelegate }
// MARK: Stream
/// Sets a closure to be called periodically during the lifecycle of the request as data is read from the server.
///
/// This closure returns the bytes most recently received from the server, not including data from previous calls.
/// If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is
/// also important to note that the server data in any `Response` object will be `nil`.
///
/// - parameter closure: The code to be executed periodically during the lifecycle of the request.
///
/// - returns: The request.
@discardableResult
open func stream(closure: ((Data) -> Void)? = nil) -> Self {
dataDelegate.dataStream = closure
return self
}
// MARK: Progress
/// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is read from the server.
///
/// - returns: The request.
@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
dataDelegate.progressHandler = (closure, queue)
return self
}
複製代碼
源碼並非很複雜,主要分紅幾個小的部分.請求,獲取請求urlRequest,經過Requestable判斷可以發起請求,能夠的話則發起請求,以後測試請求的過程當中的季度,代理等,以及請求返回的數據 DownloadRequest稍微複雜一點,由於是下載,因此無可避免的數據量會大一些,從而須要對暫停恢復等作個稍微複雜的處理
public struct DownloadOptions: OptionSet {
/// Returns the raw bitmask value of the option and satisfies the `RawRepresentable` protocol.
public let rawValue: UInt
/// A `DownloadOptions` flag that creates intermediate directories for the destination URL if specified.
public static let createIntermediateDirectories = DownloadOptions(rawValue: 1 << 0)
/// A `DownloadOptions` flag that removes a previous file from the destination URL if specified.
public static let removePreviousFile = DownloadOptions(rawValue: 1 << 1)
/// Creates a `DownloadFileDestinationOptions` instance with the specified raw value.
///
/// - parameter rawValue: The raw bitmask value for the option.
///
/// - returns: A new log level instance.
public init(rawValue: UInt) {
self.rawValue = rawValue
}
}
複製代碼
下載方式的選擇,建立中間目錄,移除以前的文件等等, 相較於Requestable來講Downloadable會稍微複雜點,是否能夠下載的話有對url的判斷,一樣還有對數據的判斷,
enum Downloadable: TaskConvertible {
case request(URLRequest)
case resumeData(Data)
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
do {
let task: URLSessionTask
switch self {
case let .request(urlRequest):
let urlRequest = try urlRequest.adapt(using: adapter)
task = queue.sync { session.downloadTask(with: urlRequest) }
case let .resumeData(resumeData):
task = queue.sync { session.downloadTask(withResumeData: resumeData) }
}
return task
} catch {
throw AdaptError(error: error)
}
}
}
複製代碼
/// The request sent or to be sent to the server.
open override var request: URLRequest? {
if let request = super.request { return request }
if let downloadable = originalTask as? Downloadable, case let .request(urlRequest) = downloadable {
return urlRequest
}
return nil
}
/// The resume data of the underlying download task if available after a failure.
open var resumeData: Data? { return downloadDelegate.resumeData }
/// The progress of downloading the response data from the server for the request.
open var progress: Progress { return downloadDelegate.progress }
var downloadDelegate: DownloadTaskDelegate { return delegate as! DownloadTaskDelegate }
// MARK: State
/// Cancels the request.
open override func cancel() {
downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }
NotificationCenter.default.post(
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task as Any]
)
}
// MARK: Progress
/// Sets a closure to be called periodically during the lifecycle of the `Request` as data is read from the server.
///
/// - parameter queue: The dispatch queue to execute the closure on.
/// - parameter closure: The code to be executed periodically as data is read from the server.
///
/// - returns: The request.
@discardableResult
open func downloadProgress(queue: DispatchQueue = DispatchQueue.main, closure: @escaping ProgressHandler) -> Self {
downloadDelegate.progressHandler = (closure, queue)
return self
}
// MARK: Destination
/// Creates a download file destination closure which uses the default file manager to move the temporary file to a
/// file URL in the first available directory with the specified search path directory and search path domain mask.
///
/// - parameter directory: The search path directory. `.DocumentDirectory` by default.
/// - parameter domain: The search path domain mask. `.UserDomainMask` by default.
///
/// - returns: A download file destination closure.
open class func suggestedDownloadDestination(
for directory: FileManager.SearchPathDirectory = .documentDirectory,
in domain: FileManager.SearchPathDomainMask = .userDomainMask)
-> DownloadFileDestination
{
return { temporaryURL, response in
let directoryURLs = FileManager.default.urls(for: directory, in: domain)
if !directoryURLs.isEmpty {
return (directoryURLs[0].appendingPathComponent(response.suggestedFilename!), [])
}
return (temporaryURL, [])
}
}
複製代碼
請求的方式同樣,固然多了幾個內容,下載失敗以後的可恢復數據,建議下載的目的地等.可是大體上都是同樣的,至於後面的UploadRequest和StreamRequest也都是大體同樣的內容而已.相較於只是細節上的一些優化.
TaskDelegate部分 由於有了request那麼相對應的有了task對象來管理這個request,一樣的,就產生了各類狀態或者進度.,那麼TaskDelegate就是task的各類協議 這塊相對來講就很簡單了主要的部分是TaskDelegate部分,其包含的主要內容是
// MARK: Properties
/// The serial operation queue used to execute all operations after the task completes.
open let queue: OperationQueue
/// The data returned by the server.
public var data: Data? { return nil }
/// The error generated throughout the lifecyle of the task.
public var error: Error?
var task: URLSessionTask? {
didSet { reset() }
}
var initialResponseTime: CFAbsoluteTime?
var credential: URLCredential?
var metrics: AnyObject? // URLSessionTaskMetrics
// MARK: Lifecycle
init(task: URLSessionTask?) {
self.task = task
self.queue = {
let operationQueue = OperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.isSuspended = true
operationQueue.qualityOfService = .utility
return operationQueue
}()
}
func reset() {
error = nil
initialResponseTime = nil
}
// MARK: URLSessionTaskDelegate
var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
var taskNeedNewBodyStream: ((URLSession, URLSessionTask) -> InputStream?)?
var taskDidCompleteWithError: ((URLSession, URLSessionTask, Error?) -> Void)?
複製代碼
有數據,有各類回調的閉包,有錯誤... 此外就是幾個協議方法
queue: OperationQueue 很明顯這是一個隊列,隊列中能夠添加不少operation。隊列是能夠被暫停的,經過把isSuspended設置爲true就可讓隊列中的全部operation暫停,直到isSuspended設置爲false後,operation纔會開始執行。在Alamofire中,放入該隊列的operation有一下幾種狀況:
注意:該隊列會在任務完成以後把isSuspended設置爲false
一個Request的endTime,在任務完成後調用,就能夠爲Request設置請求結束時間
response處理,Alamofire中的響應回調是鏈式的,原理就是把這些回調函數經過operation添加到隊列中,所以也保證了回調函數的訪問順序是正確的
上傳數據成功後,刪除臨時文件,這個後續的文章會解釋的
data: Data? 表示服務器返回的Data,這個可能爲空
error: Error?表示該代理生命週期內有可能出現的錯誤,這一點很重要,咱們在寫一個代理或者manager的時候,能夠添加這麼一個錯誤屬性,專門抓取生命週期內的錯誤。
task: URLSessionTask? 表示一個task,對於本代理而言,task是很重要的一個屬性。
initialResponseTime: CFAbsoluteTime? 當task是URLSessionDataTask時,表示接收到數據的時間;當task是URLSessionDownloadTask時,表示開始寫數據的時間;當task是URLSessionUploadTask時,表示上傳數據的時間;
credential: URLCredential? 表示證書,若是給該代理設置了這個屬性,在它裏邊的證書驗證方法中會備用到
metrics: AnyObject? apple提供了一個統計task信息的類URLSessionTaskMetrics,能夠統計跟task相關的一些信息,包括和task相關的全部事務,task的開始和結束時間,task的重定向的次數。
複製代碼
@objc(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
var redirectRequest: URLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
複製代碼
這個函數處理的問題是請求重定向問題
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if
let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
let serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluate(serverTrust, forHost: host) {
disposition = .useCredential
credential = URLCredential(trust: serverTrust)
} else {
disposition = .cancelAuthenticationChallenge
}
}
} else {
if challenge.previousFailureCount > 0 {
disposition = .rejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
completionHandler(disposition, credential)
}
複製代碼
改函數用於處理驗證相關的事務 若是服務器須要驗證客戶端的,咱們只須要給TaskDelegate的 var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?賦值就好了。 這裏有一個很重要的問題,HTTPS並不會觸發上邊的回調函數,緣由就是NSURLSession內部有一個根證書,內部會跟服務器的證書進行驗證,若是服務器的證書是證書機構頒發的話,就能夠順利經過驗證,不然會報錯。
另外一個很重要的問題是,上邊的方法在何種狀況下觸發,按照apple的說法URL Session Programming Guide,當服務器響應頭中包含WWW-Authenticate或者使用 proxy authentication TLS trust validation時,上邊的方法就會被觸發,其餘狀況下都不會觸發。
Alamofire是雙向驗證的
雙向驗證的過程:
當服務器返回的challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust時,服務器提供了一個服務器信任的證書,但這個證書也僅僅是服務器本身信任的,攻擊者徹底能夠提供一個證書騙過客戶端,基於這個問題,客戶端須要驗證服務端的證書
Alamofire中經過ServerTrustPolicy.swift這個類來驗證證書,大概過程就是拿本地的證書跟服務端返回的證書進行對比,若是客戶端存在相同的證書就表示經過,這個過程我會在ServerTrustPolicy.swift那篇文章中給出詳細的解答
複製代碼
所以,客戶端和服務端要創建SSL只須要兩步就好了:
服務端返回WWW-Authenticate響應頭,並返回本身信任證書
客戶端驗證證書,而後用證書中的公鑰把數據加密後發送給服務端
複製代碼
@objc(URLSession:task:needNewBodyStream:)
func urlSession(
_ session: URLSession,
task: URLSessionTask,
needNewBodyStream completionHandler: @escaping (InputStream?) -> Void)
{
var bodyStream: InputStream?
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
bodyStream = taskNeedNewBodyStream(session, task)
}
completionHandler(bodyStream)
}
複製代碼
當給task的Request提供一個body stream時纔會調用,咱們不須要關心這個方法,即便咱們經過fileURL或者NSData上傳數據時,該函數也不會被調用,使用場景不多。
@objc(URLSession:task:didCompleteWithError:)
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
if self.error == nil { self.error = error }
if
let downloadDelegate = self as? DownloadTaskDelegate,
let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data
{
downloadDelegate.resumeData = resumeData
}
}
queue.isSuspended = false
}
}
複製代碼
該函數在請求完成後被調用,值得注意的是error不爲nil的狀況,除了給自身的error屬性賦值外,針對下載任務作了特殊處理,就是把當前已經下載的數據保存在downloadDelegate.resumeData中,有點像斷點下載。
接下來的DataTaskDelegate繼承自TaskDelegate,實現了URLSessionDataDelegate協議
var dataTask: URLSessionDataTask { return task as! URLSessionDataTask }
override var data: Data? {
if dataStream != nil {
return nil
} else {
return mutableData
}
}
var progress: Progress
var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var dataStream: ((_ data: Data) -> Void)?
private var totalBytesReceived: Int64 = 0
private var mutableData: Data
private var expectedContentLength: Int64?
複製代碼
dataTask: URLSessionDataTask DataTaskDelegate管理URLSessionDataTask
data: Data? 一樣是返回Data,但這裏有一點不一樣,若是定義了dataStream方法的話,這個data返回爲nil
progress: Progress 進度
progressHandler 這不是函數,是一個元組,何時調用,在下邊的方法中給出說明
dataStream 自定義的數據處理函數
totalBytesReceived 已經接受的數據
mutableData 保存數據的容器
expectedContentLength 須要接受的數據的總大小
複製代碼
有四個函數
var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
var dataTaskDidBecomeDownloadTask: ((URLSession, URLSessionDataTask, URLSessionDownloadTask) -> Void)?
var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
複製代碼
第一個
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void)
{
var disposition: URLSession.ResponseDisposition = .allow
expectedContentLength = response.expectedContentLength
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
複製代碼
當收到服務端的響應後,該方法被觸發。在這個函數中,咱們可以獲取到和數據相關的一些參數,你們能夠想象成響應頭。Alamofire中給出了一個函數dataTaskDidReceiveResponse,咱們能夠利用這個函數控制是否是要繼續獲取數據,默認是.allow。
第二個
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
didBecome downloadTask: URLSessionDownloadTask)
{
dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
}
複製代碼
當咱們把URLSession.ResponseDisposition設置成becomeDownload就會觸發上邊的第二個函數,在函數中會提供一個新的downloadTask。這就給咱們一個把dataTask轉換成downloadTask的機會
第三個
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data)
} else {
mutableData.append(data)
}
let bytesReceived = Int64(data.count)
totalBytesReceived += bytesReceived
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}
複製代碼
Alamofire把數據放入對象中的過程,也是下載的核心方法..
第四個
func urlSession(
_ session: URLSession,
dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void)
{
var cachedResponse: CachedURLResponse? = proposedResponse
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
}
completionHandler(cachedResponse)
}
複製代碼
該函數用於處理是否須要緩存響應,Alamofire默認是緩存這些response的,可是每次發請求,它不會再緩存中讀取。 至於後續的DownloadTaskDelegate...基本都是差很少的實現,不通的類型處理的思路稍微不一樣...
Response部分
首先是默認的DataResponse DefaultDataResponse
public struct DefaultDataResponse {
/// The URL request sent to the server.
public let request: URLRequest?
/// The server's response to the URL request. public let response: HTTPURLResponse? /// The data returned by the server. public let data: Data? /// The error encountered while executing or validating the request. public let error: Error? /// The timeline of the complete lifecycle of the request. public let timeline: Timeline var _metrics: AnyObject? /// Creates a `DefaultDataResponse` instance from the specified parameters. /// /// - Parameters: /// - request: The URL request sent to the server. /// - response: The server's response to the URL request.
/// - data: The data returned by the server.
/// - error: The error encountered while executing or validating the request.
/// - timeline: The timeline of the complete lifecycle of the request. `Timeline()` by default.
/// - metrics: The task metrics containing the request / response statistics. `nil` by default.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
error: Error?,
timeline: Timeline = Timeline(),
metrics: AnyObject? = nil)
{
self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline
}
}
複製代碼
簡單的理解 request:URLRequest,本次訪問數據的urlRequest,包含了請求的地址,方式... response:服務器的響應,有返回的狀態碼,頭部信息等 data:服務器返回的數據,可能爲空 error:錯誤的類型 timeline:請求的完整生命週期的時間 經過一個默認的響應結果能夠看到大體其包含的信息有狀態和數據(錯誤)等,
public struct DataResponse<Value> {
/// The URL request sent to the server.
public let request: URLRequest?
/// The server's response to the URL request. public let response: HTTPURLResponse? /// The data returned by the server. public let data: Data? /// The result of response serialization. public let result: Result<Value> /// The timeline of the complete lifecycle of the request. public let timeline: Timeline /// Returns the associated value of the result if it is a success, `nil` otherwise. public var value: Value? { return result.value } /// Returns the associated error value if the result if it is a failure, `nil` otherwise. public var error: Error? { return result.error } var _metrics: AnyObject? /// Creates a `DataResponse` instance with the specified parameters derived from response serialization. /// /// - parameter request: The URL request sent to the server. /// - parameter response: The server's response to the URL request.
/// - parameter data: The data returned by the server.
/// - parameter result: The result of response serialization.
/// - parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`.
///
/// - returns: The new `DataResponse` instance.
public init(
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
result: Result<Value>,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
self.data = data
self.result = result
self.timeline = timeline
}
}
複製代碼
dataResponse部分,基本都是同樣的內容,須要看的是其擴展的部分
/// Evaluates the specified closure when the result of this `DataResponse` is a success, passing the unwrapped
/// result value as a parameter.
///
/// Use the `map` method with a closure that does not throw. For example:
///
/// let possibleData: DataResponse<Data> = ...
/// let possibleInt = possibleData.map { $0.count }
///
/// - parameter transform: A closure that takes the success value of the instance's result. /// /// - returns: A `DataResponse` whose result wraps the value returned by the given closure. If this instance's
/// result is a failure, returns a response wrapping the same failure.
public func map<T>(_ transform: (Value) -> T) -> DataResponse<T> {
var response = DataResponse<T>(
request: request,
response: self.response,
data: data,
result: result.map(transform),
timeline: timeline
)
response._metrics = _metrics
return response
}
/// Evaluates the given closure when the result of this `DataResponse` is a success, passing the unwrapped result
/// value as a parameter.
///
/// Use the `flatMap` method with a closure that may throw an error. For example:
///
/// let possibleData: DataResponse<Data> = ...
/// let possibleObject = possibleData.flatMap {
/// try JSONSerialization.jsonObject(with: $0)
/// }
///
/// - parameter transform: A closure that takes the success value of the instance's result. /// /// - returns: A success or failure `DataResponse` depending on the result of the given closure. If this instance's
/// result is a failure, returns the same failure.
public func flatMap<T>(_ transform: (Value) throws -> T) -> DataResponse<T> {
var response = DataResponse<T>(
request: request,
response: self.response,
data: data,
result: result.flatMap(transform),
timeline: timeline
)
response._metrics = _metrics
return response
}
}
複製代碼
主要的是兩個過濾及序列化的方式 再接下來的DownloadResponse..也差很少是同樣的....
ResponseSerialization部分 有了服務器的返回數據那麼確定就有解析數據的, 至因而什麼樣的序列化方式...
public protocol DataResponseSerializerProtocol {
/// The type of serialized object to be created by this `DataResponseSerializerType`.
associatedtype SerializedObject
/// A closure used by response handlers that takes a request, response, data and error and returns a result.
var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<SerializedObject> { get }
}
複製代碼
DataResponseSerializer協議,返回的是一個SerializedObject的對象
首先是DataResponseSerializer
public struct DataResponseSerializer<Value>: DataResponseSerializerProtocol {
/// The type of serialized object to be created by this `DataResponseSerializer`.
public typealias SerializedObject = Value
/// A closure used by response handlers that takes a request, response, data and error and returns a result.
public var serializeResponse: (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>
/// Initializes the `ResponseSerializer` instance with the given serialize response closure.
///
/// - parameter serializeResponse: The closure used to serialize the response.
///
/// - returns: The new generic response serializer instance.
public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, Data?, Error?) -> Result<Value>) {
self.serializeResponse = serializeResponse
}
}
複製代碼
沒有序列化的,直接返回HTTPResponse
/// The type in which all download response serializers must conform to in order to serialize a response.
public protocol DownloadResponseSerializerProtocol {
/// The type of serialized object to be created by this `DownloadResponseSerializerType`.
associatedtype SerializedObject
/// A closure used by response handlers that takes a request, response, url and error and returns a result.
var serializeResponse: (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result<SerializedObject> { get }
}
// MARK: -
/// A generic `DownloadResponseSerializerType` used to serialize a request, response, and data into a serialized object.
public struct DownloadResponseSerializer<Value>: DownloadResponseSerializerProtocol {
/// The type of serialized object to be created by this `DownloadResponseSerializer`.
public typealias SerializedObject = Value
/// A closure used by response handlers that takes a request, response, url and error and returns a result.
public var serializeResponse: (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result<Value>
/// Initializes the `ResponseSerializer` instance with the given serialize response closure.
///
/// - parameter serializeResponse: The closure used to serialize the response.
///
/// - returns: The new generic response serializer instance.
public init(serializeResponse: @escaping (URLRequest?, HTTPURLResponse?, URL?, Error?) -> Result<Value>) {
self.serializeResponse = serializeResponse
}
}
複製代碼
跟DataResponseSerializer相似
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter 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.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter responseSerializer: The response serializer responsible for serializing the request, response,
/// and data.
/// - parameter 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 = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
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 ?? DispatchQueue.main).async { completionHandler(dataResponse) }
}
return self
}
}
複製代碼
兩個方式大體是差很少的,都是給請求添加一個func,實現請求完成以後的回調, 第一個使用的是默認的DefaultDataResponse類型的結果,第二個則是自定義的實現DataResponseSerializerProtocol的結果,
結果序列化成Data的
// MARK: - Data
extension Request {
/// Returns a result data type that contains the response data as-is.
///
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponseData(response: HTTPURLResponse?, data: Data?, error: Error?) -> Result<Data> {
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(Data()) }
guard let validData = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
return .success(validData)
}
}
extension DataRequest {
/// Creates a response serializer that returns the associated data as-is.
///
/// - returns: A data response serializer.
public static func dataResponseSerializer() -> DataResponseSerializer<Data> {
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseData(response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter 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: DataRequest.dataResponseSerializer(),
completionHandler: completionHandler
)
}
}
複製代碼
結果序列化成字符串
// MARK: - String
extension Request {
/// Returns a result string type initialized from the response data with the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponseString(
encoding: String.Encoding?,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<String>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success("") }
guard let validData = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
var convertedEncoding = encoding
if let encodingName = response?.textEncodingName as CFString!, convertedEncoding == nil {
convertedEncoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(
CFStringConvertIANACharSetNameToEncoding(encodingName))
)
}
let actualEncoding = convertedEncoding ?? String.Encoding.isoLatin1
if let string = String(data: validData, encoding: actualEncoding) {
return .success(string)
} else {
return .failure(AFError.responseSerializationFailed(reason: .stringSerializationFailed(encoding: actualEncoding)))
}
}
}
extension DataRequest {
/// Creates a response serializer that returns a result string type initialized from the response data with
/// the specified string encoding.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
/// response, falling back to the default HTTP default character set, ISO-8859-1.
///
/// - returns: A string response serializer.
public static func stringResponseSerializer(encoding: String.Encoding? = nil) -> DataResponseSerializer<String> {
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseString(encoding: encoding, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the
/// server response, falling back to the default HTTP default character set,
/// ISO-8859-1.
/// - parameter 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: DataRequest.stringResponseSerializer(encoding: encoding),
completionHandler: completionHandler
)
}
}
複製代碼
結果序列化成JSON
// MARK: - JSON
extension Request {
/// Returns a JSON object contained in a result type constructed from the response data using `JSONSerialization`
/// with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponseJSON(
options: JSONSerialization.ReadingOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<Any>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let json = try JSONSerialization.jsonObject(with: validData, options: options)
return .success(json)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
}
}
}
extension DataRequest {
/// Creates a response serializer that returns a JSON object result type constructed from the response data using
/// `JSONSerialization` with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
///
/// - returns: A JSON object response serializer.
public static func jsonResponseSerializer(
options: JSONSerialization.ReadingOptions = .allowFragments)
-> DataResponseSerializer<Any>
{
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter 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: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
複製代碼
結果序列化成Any
// MARK: - Property List
extension Request {
/// Returns a plist object contained in a result type constructed from the response data using
/// `PropertyListSerialization` with the specified reading options.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
/// - parameter response: The response from the server.
/// - parameter data: The data returned from the server.
/// - parameter error: The error already encountered if it exists.
///
/// - returns: The result data type.
public static func serializeResponsePropertyList(
options: PropertyListSerialization.ReadOptions,
response: HTTPURLResponse?,
data: Data?,
error: Error?)
-> Result<Any>
{
guard error == nil else { return .failure(error!) }
if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(NSNull()) }
guard let validData = data, validData.count > 0 else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength))
}
do {
let plist = try PropertyListSerialization.propertyList(from: validData, options: options, format: nil)
return .success(plist)
} catch {
return .failure(AFError.responseSerializationFailed(reason: .propertyListSerializationFailed(error: error)))
}
}
}
extension DataRequest {
/// Creates a response serializer that returns an object constructed from the response data using
/// `PropertyListSerialization` with the specified reading options.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
///
/// - returns: A property list object response serializer.
public static func propertyListResponseSerializer(
options: PropertyListSerialization.ReadOptions = [])
-> DataResponseSerializer<Any>
{
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponsePropertyList(options: options, response: response, data: data, error: error)
}
}
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The property list reading options. Defaults to `[]`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
@discardableResult
public func responsePropertyList(
queue: DispatchQueue? = nil,
options: PropertyListSerialization.ReadOptions = [],
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.propertyListResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
複製代碼
Result部分 結果已經序列化或者自定成想要的格式了,那麼對應的一個請求就完成了本次相應的完整的請求,那麼對應的回調結果就應該返回序列化以後的結果 響應有成功,有失敗,相對應的就有了兩種不一樣的result
public enum Result<Value> {
case success(Value)
case failure(Error)
/// Returns `true` if the result is a success, `false` otherwise.
public var isSuccess: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}
/// Returns `true` if the result is a failure, `false` otherwise.
public var isFailure: Bool {
return !isSuccess
}
/// Returns the associated value if the result is a success, `nil` otherwise.
public var value: Value? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}
/// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}
複製代碼
跟上面說的同樣,首先是枚舉,成功或者失敗,另外這裏用了泛型 泛型的寫法是相似這樣的:,在<和>之間聲明一種類型,這個T知識象徵性的,在賦值的時候,能夠是任何類型 咱們從上面講到的responseJSON中能夠看到
@discardableResult
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
複製代碼
上邊的這個函數的主要目的是把請求成功後的結果序列化爲JSON,completionHandler函數的參數類型爲DataResponse,其中的Any就會傳遞給Result,也就是Result。
那麼問題來了,不是把數據解析成JSON了嗎?爲何要返回Any類型呢?json本質上很相似於JavaScript中的對象和數組。JSONSerialization.jsonObject返回的類型是Any,這是由於解析後的數據有多是數組,也有多是字典。固然若是不是這兩種格式的數據,使用JSONSerialization.jsonObject解析會拋出異常。
另外,result添加了一些屬性方便應用使用...總起來講,Result是一個比較簡單的封裝。
差很少一次網絡請求的整個過程都已經解析完畢了,那麼咱們確定不能這個鬆散的,咱們須要一個會話管理來控制每個請求,控制併發,控制進度等.
SessionManager部分
大的來講,這是一個整合,把整個請求過程封裝成API的方式方便咱們調用,同時做爲一個會話管理,它也負責着併發等複雜處理
open static let defaultHTTPHeaders: HTTPHeaders = {
// Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
// Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
let acceptLanguage = Locale.preferredLanguages.prefix(6).enumerated().map { index, languageCode in
let quality = 1.0 - (Double(index) * 0.1)
return "\(languageCode);q=\(quality)"
}.joined(separator: ", ")
// User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0`
let userAgent: String = {
if let info = Bundle.main.infoDictionary {
let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
let appVersion = info["CFBundleShortVersionString"] as? String ?? "Unknown"
let appBuild = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
let osNameVersion: String = {
let version = ProcessInfo.processInfo.operatingSystemVersion
let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
let osName: String = {
#if os(iOS)
return "iOS"
#elseif os(watchOS)
return "watchOS"
#elseif os(tvOS)
return "tvOS"
#elseif os(macOS)
return "OS X"
#elseif os(Linux)
return "Linux"
#else
return "Unknown"
#endif
}()
return "\(osName) \(versionString)"
}()
let alamofireVersion: String = {
guard
let afInfo = Bundle(for: SessionManager.self).infoDictionary,
let build = afInfo["CFBundleShortVersionString"]
else { return "Unknown" }
return "Alamofire/\(build)"
}()
return "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
}
return "Alamofire"
}()
return [
"Accept-Encoding": acceptEncoding,
"Accept-Language": acceptLanguage,
"User-Agent": userAgent
]
}()
複製代碼
上面的內容是一個默認的defaultHTTPHeaders
open static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
複製代碼
返回一個默認的會話管理單例
@discardableResult
open func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest
{
var originalRequest: URLRequest?
do {
originalRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
return request(encodedURLRequest)
} catch {
return request(originalRequest, failedWith: error)
}
}
複製代碼
請求函數,傳入url,參數,請求方式等.調用請求,並返回請求DataRequest,
/// Creates a `DataRequest` to retrieve the contents of a URL based on the specified `urlRequest`.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter urlRequest: The URL request.
///
/// - returns: The created `DataRequest`.
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
var originalRequest: URLRequest?
do {
originalRequest = try urlRequest.asURLRequest()
let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
delegate[task] = request
if startRequestsImmediately { request.resume() }
return request
} catch {
return request(originalRequest, failedWith: error)
}
}
複製代碼
根據request 添加響應的delegate,隊列,並resume發起請求,同時返回DataRequest
private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest {
var requestTask: Request.RequestTask = .data(nil, nil)
if let urlRequest = urlRequest {
let originalTask = DataRequest.Requestable(urlRequest: urlRequest)
requestTask = .data(originalTask, nil)
}
let underlyingError = error.underlyingAdaptError ?? error
let request = DataRequest(session: session, requestTask: requestTask, error: underlyingError)
if let retrier = retrier, error is AdaptError {
allowRetrier(retrier, toRetry: request, with: underlyingError)
} else {
if startRequestsImmediately { request.resume() }
}
return request
}
複製代碼
一樣的請求,是在alamofire嘗試編碼失敗以後直接發起請求
// MARK: - Download Request
// MARK: URL Request
/// Creates a `DownloadRequest` to retrieve the contents the specified `url`, `method`, `parameters`, `encoding`,
/// `headers` and save them to the `destination`.
///
/// If `destination` is not specified, the contents will remain in the temporary location determined by the
/// underlying URL session.
///
/// If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
/// - parameter destination: The closure used to determine the destination of the downloaded file. `nil` by default.
///
/// - returns: The created `DownloadRequest`.
@discardableResult
open func download(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
do {
let urlRequest = try URLRequest(url: url, method: method, headers: headers)
let encodedURLRequest = try encoding.encode(urlRequest, with: parameters)
return download(encodedURLRequest, to: destination)
} catch {
return download(nil, to: destination, failedWith: error)
}
}
複製代碼
download,相似於data,經過地址,參數,方式等建立一個DownloadRequest
@discardableResult
open func download(
_ urlRequest: URLRequestConvertible,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
do {
let urlRequest = try urlRequest.asURLRequest()
return download(.request(urlRequest), to: destination)
} catch {
return download(nil, to: destination, failedWith: error)
}
}
複製代碼
發起下載請求
@discardableResult
open func download(
resumingWith resumeData: Data,
to destination: DownloadRequest.DownloadFileDestination? = nil)
-> DownloadRequest
{
return download(.resumeData(resumeData), to: destination)
}
複製代碼
恢復下載,從某data開始
// MARK: Private - Download Implementation
private func download(
_ downloadable: DownloadRequest.Downloadable,
to destination: DownloadRequest.DownloadFileDestination?)
-> DownloadRequest
{
do {
let task = try downloadable.task(session: session, adapter: adapter, queue: queue)
let download = DownloadRequest(session: session, requestTask: .download(downloadable, task))
download.downloadDelegate.destination = destination
delegate[task] = download
if startRequestsImmediately { download.resume() }
return download
} catch {
return download(downloadable, to: destination, failedWith: error)
}
}
複製代碼
下載的實現調用
後面的update..也差很少都是這樣的方式, 總的來講經過SessionManager提供的api咱們能夠很簡單的發起一次請求, 再經過ResponseSerialization中對request的extension,咱們能夠調用如responseString\responseData等方法獲取到結果返回的回調. 這樣一次完整的網絡請求基本就完成了,基礎的話,咱們能夠不用管每次請求,響應,管理等各類狀態的協議,咱們基本不須要再去實現,固然有特殊需求的時候,咱們只要在合適的地方實現協議便可 在咱們使用時 咱們僅須要如此便可
Alamofire.request("https://www.baidu.com").responseString { (response) in
print(response.result.value ?? "")
}
複製代碼
固然咱們也能夠針對本身應用中的環境在加一層封裝 例如我有一個項目
static let shared = XLXNetManager()
...
fileprivate class func get(url:URLConvertible, parameters : Parameters, success : @escaping (_ value : String)->(), failure : @escaping (_ error : Error)->()) {
Alamofire
.request(url, method: .get, parameters: appendAutoParametersDictionary(parameters), encoding: URLEncoding.default, headers: nil)
.responseString { (response) in
switch response.result {
case .success(let value):
success(value)
case .failure(let error):
XLXLog("error:\(error)")
failure(error)
}
}
}
fileprivate class func post(url:URLConvertible, parameters : Parameters, success : @escaping (_ value : String)->(), failure : @escaping (_ error : Error)->()) {
Alamofire
.request(url, method: .post, parameters: appendAutoParametersDictionary(parameters), encoding: URLEncoding.default, headers: nil)
.responseString { (response) in
switch response.result {
case .success(let value):
success(value)
case .failure(let error):
XLXLog("error:\(error)")
failure(error)
}
}
}
class func get_userHelp(success : @escaping (_ value : String)->(), failure : @escaping (_ error : Error)->()) {
get(url: userHelpUrl, parameters: [:], success: success, failure: failure)
}
複製代碼
這樣我在使用的時候
XLXNetManager.get_userHelp(success: {(ressult) in
//利用如HandyJSON等三方或者系統,進行模型轉換....
XLXLog(ressult)
}) { (error) in
XLXLog(error)
}
複製代碼
徹底的相似AFNetworking時候的使用方式.... 固然,這個是自由的.任你發揮
未完待續,接下來還會簡單介紹其餘非關鍵的類..