Alamofire源码解读
AFNetworking的作者Matt Thompson 提出了一个新的类似AFNetworking的网络基础库,并且专门使用最新的Swift语言写的,名为 Alamofire.
对于使用OC的开发者来说一定十分熟悉AFNetworking这个框架,因为现在我们的app只要是有关于网络访问的部分大部分都会通过这个框架来进行网络的访问。
而Alamofire 是 Swift 语言的 HTTP 网络开发工具包,功能强大,支持各种 HTTP Method、JSON、文件上传、文件下载和多种认证方法。
所以当我们采用Swift进行开发的时候,我们就有了更多的选择,是采用OC的AFNetworking也好,还是采用Swift的Alamofire也行...
相信作为开发者,大家都在其他地方或多或少的读过关于AFNetworking的解析,有的甚至也自己做过AFNetworking的解析,
那么为什么还要再做Alamofire的解析呢?
原因有以下几点,
- 新的开发语言,新的开发思路
- 学习Swift规范开发的一个很好的教程
- 网络流程的学习,请求、响应、解析、等等...
- AFAFNetworking和Alamofire之间的对比
OK 进入正题.
首先作为一个新的网络库,Alamofire同时作为AFnetworking的Swift版本,甚至是作为同一个作者的开源库,无论从哪个角度来说,Alamofire都有很多和AFnetworking相似的地方...
如何对比,我们首先把Alamofire加上项目中看看
网上很容易找到方法,直接加入项目,或者是采用CocoaPods进行安装.
这里我也简单的展示一下
第一种方式
我们下载这个库文件
https://github.com/Alamofire/Alamofire
我们创建一个项目并把alamofire拖到整个项目根目录中
打开工程后选择Alamofire.xcodeproj 拖入工程中
选择自己的工程.xcodeproj->Build Phases->Link Binary With Libraries添加Alamofire.framework
之后可能需要在info.plist中添加(如果你的网路请求地址是http的话)
在需要的网络请求的地方类似这样使用即可
这样手动导入框架就已经完成了
当然作为开发者,大部分都会使用CocoaPods的方式,这里我就不啰嗦了,大家自行百度使用CocoaPods的方式即可
OK,我们这里先不看使用的方式,我们开始解读源码,我们打开Alamofire我们可以看到所有的文件
如果使用的是CocoaPods的话也可以看到相同的
总共有17个文件
- Alamofire-->基本接口的封装
- AFError-->错误的封装
- Notifications-->通知的封装
- ParameterEncoding-->参数编码的封装
- Request-->请求的封装 (核心)
- Response-->服务器返回数据的封装
- Result-->请求结果的封装
- SessionDelegate-->会话代理的封装
- SessionManager-->会话管理的封装 (核心)
- TaskDelegate-->任务代理的封装
- DispatchQueue+Alamofire-->GCD的封装
- MultipartFormData-->多表单数据的封装
- NetworkReachabilityManager-->网络状态管理的封装
- ResponseSerialization-->响应序列化管理的封装
- ServerTrustPolicy-->安全策略的封装
- Timeline-->时间轴的封装
- Validation-->服务器响应的验证
我会按照网络请求的整个过程来逐一解析
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>,在<和>之间声明一种类型,这个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>,其中的Any就会传递给Result,也就是Result<Any>。
那么问题来了,不是把数据解析成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时候的使用方式....
当然,这个是自由的.任你发挥
未完待续,接下来还会简单介绍其他非关键的类..