原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易,請(qǐng)珍惜,之后會(huì)持續(xù)更新,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈,這些文章記錄了我的IOS成長(zhǎng)歷程,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn),大家可通過(guò)command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容
目錄
- 一、抓包工具
- 1、Charles 抓包工具
- 2、Fiddler Everywhere 抓包工具
- 二、Alamofire 架構(gòu)
- 1、接口
- 2、請(qǐng)求
- 3、響應(yīng)
- 4、底層
- 5、其他
- 三、核心流程
- 四、功能點(diǎn)解析
- 1、AF
- 2、Request
- 3、HTTPMethod
- 4、ParameterEncoding
- 5、Validation
- 6、DispatchQueue
- 7、AFError
- 8、DataResponse
- Demo
- 參考文獻(xiàn)
一、抓包工具
1、Charles 抓包工具

a、配置各種證書比較麻煩

b、Charles抓包工具總是會(huì)瘋狂報(bào)錯(cuò)(這個(gè)問(wèn)題可以重裝Charles抓包工具解決掉)

c、而且還存在抓取的內(nèi)容亂碼顯示問(wèn)題

這個(gè)問(wèn)題解決起來(lái)比較麻煩,我差不多都快放棄使用這個(gè)工具了,突然卻被我解決掉這個(gè)問(wèn)題了,在這里畫個(gè)圈圈詛咒軟件的設(shè)計(jì)者,你這么復(fù)雜的配置讓人怎么玩你?
在電腦端安裝并信任證書


在Charles中,設(shè)置ssl proxy Setting中的ssl proxying的代理網(wǎng)址,按圖中填寫即可,這一步非常重要,我就是設(shè)置了這一步后才沒(méi)有亂碼的

設(shè)置抓包的網(wǎng)址和端口,設(shè)置為全部都抓


大功告成,終于抓取到正常的內(nèi)容了,留下了喜極而泣的淚水,沒(méi)忍住想把創(chuàng)作者拖出去打的沖動(dòng)。

2、Fiddler Everywhere 抓包工具
推薦使用簡(jiǎn)單明了的Fiddler Everywhere抓包工具,這是它的下載地址 Fiddler Everywhere,免費(fèi)版本的就可以用得很順溜了。

界面整體看起來(lái)也比較干凈簡(jiǎn)潔,而且安裝好以后就可以直接使用,不用處理一大堆的配置問(wèn)題。

和其它抓包工具一樣,Fiddler Everywhere默認(rèn)也是只能抓取HTTP請(qǐng)求,需要通過(guò)下載證書或進(jìn)行相關(guān)配置,才能正常攔截HTTPS請(qǐng)求,配置如下,信任證書并勾選捕捉。

二、Alamofire 架構(gòu)
1、接口
Alamofire.swift:api 聲明
// 全局靜態(tài)變量
public let AF = Session.default
// 當(dāng)前Alamofire版本
let version = "5.4.1"
2、請(qǐng)求
Request.swift:請(qǐng)求類,用于構(gòu)建請(qǐng)求
public protocol RequestDelegate: AnyObject
public class Request
public class DataRequest: Request
public final class DataStreamRequest: Request
public class DownloadRequest: Request
public class UploadRequest: DataRequest
ParameterEncoding.swift:參數(shù)編碼
public protocol ParameterEncoder
open class JSONParameterEncoder: ParameterEncoder
open class URLEncodedFormParameterEncoder: ParameterEncoder
MultipartFormData.swift:自定義表單類
open class MultipartFormData
ServerTrustEvaluation.swift:服務(wù)器驗(yàn)證
public protocol ServerTrustEvaluating
open class ServerTrustManager
public final class DefaultTrustEvaluator: ServerTrustEvaluating
public final class RevocationTrustEvaluator: ServerTrustEvaluating
public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating
public final class PublicKeysTrustEvaluator: ServerTrustEvaluating
public final class CompositeTrustEvaluator: ServerTrustEvaluating
public final class DisabledTrustEvaluator: ServerTrustEvaluating
3、響應(yīng)
Response.swift:響應(yīng)類,用于構(gòu)建響應(yīng)
public struct DataResponse<Success, Failure: Error>
public struct DownloadResponse<Success, Failure: Error>
Validation.swift:響應(yīng)數(shù)據(jù)驗(yàn)證
extension Request
extension DataRequest
extension DataStreamRequest
extension DownloadRequest
Result+Alamofire.swift:請(qǐng)求結(jié)果表示
extension Result
AFError.swift:錯(cuò)誤類型
public enum AFError: Error
public enum MultipartEncodingFailureReason
public enum ParameterEncodingFailureReason
public enum ParameterEncoderFailureReason
public enum ResponseValidationFailureReason
public enum ResponseSerializationFailureReason
public enum ServerTrustFailureReason
public enum URLRequestValidationFailureReason
4、底層
Session.swift:請(qǐng)求session的管理類,底層使用NSURLSession實(shí)現(xiàn)
open class Session
MARK: - DataRequest
MARK: - DataStreamRequest
MARK: - UploadRequest
SessionDelegate.swift:請(qǐng)求Session的代理對(duì)象,主要實(shí)現(xiàn)NSURLSession的代理方法以及回調(diào)閉包
open class SessionDelegate: NSObject
extension SessionDelegate: URLSessionDelegate
extension SessionDelegate: URLSessionTaskDelegate
extension SessionDelegate: URLSessionDataDelegate
extension SessionDelegate: URLSessionDownloadDelegate
DispatchQueue+Alamofire.swift:GCD擴(kuò)展,增加了一個(gè)名為after的延遲調(diào)用方法
extension DispatchQueue
5、其他
NetworkReachabilityManager.swift:網(wǎng)絡(luò)狀態(tài)監(jiān)聽類
open class NetworkReachabilityManager
Notifications.swift:定義通知
extension Notification
extension NotificationCenter
public final class AlamofireNotifications: EventMonitor
三、核心流程
這里并不會(huì)貼上源碼里的大段代碼,而是通過(guò)一個(gè)例子來(lái)進(jìn)入到Alamofire中的各個(gè)模塊,讓讀者更好理解。
a、安全認(rèn)證
在info.plist中添加App Transport Security Settings,再將Allow Arbitrary Loads修改為YES后控制臺(tái)輸出結(jié)果為如下:

b、核心流程的范例
// 淘寶的一個(gè)搜索api
let url = "http://suggest.taobao.com/sug"
// 對(duì)襪子進(jìn)行搜索
let parameters: [String: Any] = [
"code" : "utf-8",
"q" : "襪子"
]
AF.request(url, method: .get, parameters: parameters)
.validate(statusCode: [200])
.responseData(queue: DispatchQueue.global())
{ (responseData) in
switch responseData.result
{
case .success(let data):
guard let jsonString = String(data: data, encoding: .utf8) else { return }
print("json字符串:\(jsonString)")
case .failure(let error):
print("錯(cuò)誤信息:\(error)")
}
}
c、范例中的功能點(diǎn)解析
- 通過(guò)
Alamofire的request開始調(diào)用 - 傳入
url - 使用
get方法 - 傳入
parameters,并在encoding中定義了參數(shù)的編碼方式 - 這里并不需要
headers - 在
validate中傳入需要驗(yàn)證的statusCode的數(shù)組 - 在
responseData方法里傳入全局隊(duì)列和回調(diào)的處理閉包completionHandler - 在閉包里對(duì)返回的數(shù)據(jù)進(jìn)行判斷,若返回成功,打印
jsonString,若失敗則打印錯(cuò)誤
d、輸出結(jié)果
json字符串:
{"result":[["襪子女","988016.6933754483"],["襪子男","865386.2105082254"],["襪子女冬","662313.8682009963"],["襪子女中筒襪","390508.191495028"],["襪子男長(zhǎng)襪","650152.593118986"],["襪子男冬","747552.2895411798"],["襪子女ins潮","286597.0935191857"],["襪子男純棉","324485.1344208922"],["襪子女冬季 加絨","329439.1491313167"],["襪子男中筒","300330.61181601905"]]}
四、功能點(diǎn)解析
1、AF
a、全局靜態(tài)變量
AF是最外層用于發(fā)起網(wǎng)絡(luò)請(qǐng)求的靜態(tài)變量,來(lái)自于全局靜態(tài)變量Session.default
- 降低使用門檻,將復(fù)雜的功能實(shí)現(xiàn)進(jìn)行下沉
- 提供單一接口來(lái)實(shí)現(xiàn)功能上的全面覆蓋,沒(méi)有多余代碼,非常簡(jiǎn)潔
- 方便編程者解讀源碼,探究SDK的封裝思路
public let AF = Session.default
default是提供給全局使用的共享單例
open class Session
{
public static let default = Session()
}
b、請(qǐng)求實(shí)際的調(diào)用者是URLSession
通過(guò)使用 URLSession 來(lái)初始化 Session
public init(session: URLSession,
delegate: SessionDelegate,
rootQueue: DispatchQueue,
startRequestsImmediately: Bool = true,
requestQueue: DispatchQueue? = nil,
serializationQueue: DispatchQueue? = nil,
interceptor: RequestInterceptor? = nil,
serverTrustManager: ServerTrustManager? = nil,
redirectHandler: RedirectHandler? = nil,
cachedResponseHandler: CachedResponseHandler? = nil,
eventMonitors: [EventMonitor] = [])
通過(guò)使用URLSessionConfiguration來(lái)初始化 Session,最終還是使用了URLSession
public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
delegate: SessionDelegate = SessionDelegate(),
rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
startRequestsImmediately: Bool = true,
requestQueue: DispatchQueue? = nil,
serializationQueue: DispatchQueue? = nil,
interceptor: RequestInterceptor? = nil,
serverTrustManager: ServerTrustManager? = nil,
redirectHandler: RedirectHandler? = nil,
cachedResponseHandler: CachedResponseHandler? = nil,
eventMonitors: [EventMonitor] = [])
{
let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: rootQueue, name: "org.alamofire.session.sessionDelegateQueue")
let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)
self.init(session: session,
delegate: delegate,
rootQueue: rootQueue,
startRequestsImmediately: startRequestsImmediately,
requestQueue: requestQueue,
serializationQueue: serializationQueue,
interceptor: interceptor,
serverTrustManager: serverTrustManager,
redirectHandler: redirectHandler,
cachedResponseHandler: cachedResponseHandler,
eventMonitors: eventMonitors)
}
2、Request
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest
open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest
a、URLConvertible協(xié)議
我們?cè)诶永飩魅氲?code>url是String類型,也可以將它轉(zhuǎn)成URL類型再傳入,結(jié)果都正確。這是因?yàn)?code>String和URL都遵循URLConvertible協(xié)議,通過(guò)實(shí)現(xiàn)協(xié)議里的asURL()方法,從String、URL或URLComponents類型轉(zhuǎn)換為URL,若失敗則拋出為AFError的錯(cuò)誤。
public protocol URLConvertible
{
func asURL() throws -> URL
}
extension String: URLConvertible
{
public func asURL() throws -> URL
{
guard let url = URL(string: self)
return url
}
}
extension URL: URLConvertible
{
public func asURL() throws -> URL { self }
}
extension URLComponents: URLConvertible
{
public func asURL() throws -> URL
{
// 如果轉(zhuǎn)換 url 失敗, 拋出一個(gè)異常
guard let url = url else { throw AFError.invalidURL(url: self) }
return url
}
}
b、URLRequestConvertible協(xié)議
負(fù)責(zé)URLRequest的轉(zhuǎn)換,和上面類似,就不多介紹了。
// 可以轉(zhuǎn)換成 urlrequest 的協(xié)議
public protocol URLRequestConvertible
{
// 返回一個(gè) urlrequest, 如果有錯(cuò), 可以拋出異常
func asURLRequest() throws -> URLRequest
}
// 創(chuàng)建 urlRequest
extension URLRequestConvertible
{
public var urlRequest: URLRequest? { try? asURLRequest() }
}
// 返回自身
extension URLRequest: URLRequestConvertible
{
public func asURLRequest() throws -> URLRequest { self }
}
c、返回Request的子類
? 包括request、download、upload等方法都是傳入所需參數(shù),發(fā)起請(qǐng)求,再返回Request的子類
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil) -> DataRequest
open func download(_ convertible: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
requestModifier: RequestModifier? = nil,
to destination: DownloadRequest.Destination? = nil) -> DownloadRequest
open func upload(_ data: Data,
to convertible: URLConvertible,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
fileManager: FileManager = .default,
requestModifier: RequestModifier? = nil) -> UploadRequest
? 返回給我們的Request,可以讓我們控制請(qǐng)求的暫停,恢復(fù),取消等
請(qǐng)求狀態(tài)
fileprivate var mutableState = MutableState()
public var state: State { mutableState.state }
public var isInitialized: Bool { state == .initialized }// 初始化
public var isResumed: Bool { state == .resumed }// 恢復(fù)請(qǐng)求
public var isSuspended: Bool { state == .suspended }// 暫停請(qǐng)求
public var isCancelled: Bool { state == .cancelled }// 取消請(qǐng)求
public var isFinished: Bool { state == .finished }// 完成請(qǐng)求
暫停請(qǐng)求
public func suspend() -> Self
{
$mutableState.write
{ mutableState in
// 如果不能暫停, 那么就跳過(guò)
guard mutableState.state.canTransitionTo(.suspended) else { return }
// 變更可變狀態(tài)為暫停
mutableState.state = .suspended
// 在下層隊(duì)列中更新暫停狀態(tài),didSuspend()在暫停完成時(shí)調(diào)用
underlyingQueue.async { self.didSuspend() }
guard let task = mutableState.tasks.last, task.state != .completed else { return }
// 調(diào)用真正的網(wǎng)絡(luò)請(qǐng)求URLSessionTask的suspend()方法
task.suspend()
// 在下層隊(duì)列中更新暫停狀態(tài),didSuspendTask()在URLSessionTask暫停時(shí)調(diào)用
underlyingQueue.async { self.didSuspendTask(task) }
}
return self
}
取消請(qǐng)求
public func cancel() -> Self
{
$mutableState.write
{ mutableState in
guard mutableState.state.canTransitionTo(.cancelled) else { return }
mutableState.state = .cancelled
underlyingQueue.async { self.didCancel() }
// Resume to ensure metrics are gathered.
task.resume()
// 取消
task.cancel()
underlyingQueue.async { self.didCancelTask(task) }
}
return self
}
恢復(fù)請(qǐng)求
public func resume() -> Self
{
$mutableState.write { mutableState in
guard mutableState.state.canTransitionTo(.resumed) else { return }
mutableState.state = .resumed
underlyingQueue.async { self.didResume() }
guard let task = mutableState.tasks.last, task.state != .completed else { return }
task.resume()
underlyingQueue.async { self.didResumeTask(task) }
}
return self
}
did方法,以恢復(fù)請(qǐng)求為例。
func didResume()
{
dispatchPrecondition(condition: .onQueue(underlyingQueue))
eventMonitor?.requestDidResume(self)
}
func didResumeTask(_ task: URLSessionTask)
{
dispatchPrecondition(condition: .onQueue(underlyingQueue))
eventMonitor?.request(self, didResumeTask: task)
}
3、HTTPMethod
a、HTTPMethod是個(gè)結(jié)構(gòu)體
public struct HTTPMethod: RawRepresentable, Equatable, Hashable
{
public static let connect = HTTPMethod(rawValue: "CONNECT")
public static let delete = HTTPMethod(rawValue: "DELETE")
public static let get = HTTPMethod(rawValue: "GET")
public static let head = HTTPMethod(rawValue: "HEAD")
public static let post = HTTPMethod(rawValue: "POST")
...
public let rawValue: String
public init(rawValue: String)
{
self.rawValue = rawValue
}
}
b、打印HTTPMethod的值
open func request(_ convertible: URLConvertible,
method: HTTPMethod = .get,
...
{
print("打印HTTPMethod的枚舉:\(method)")
print("打印HTTPMethod的枚舉關(guān)聯(lián)值:\(method.rawValue)")
......
}
輸出結(jié)果
打印HTTPMethod的值:HTTPMethod(rawValue: "GET")
打印HTTPMethod的原始值:GET
c、會(huì)話配置
? Session的初始化方法中使用了默認(rèn)的會(huì)話配置
public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default....)
? 使用了系統(tǒng)提供的默認(rèn)會(huì)話配置,并為其配置了默認(rèn)的HTTP的頭部信息
public static var default: URLSessionConfiguration
{
let configuration = URLSessionConfiguration.default
configuration.headers = .default
return configuration
}
? HTTP默認(rèn)的頭部信息包括User-Agent、Accept-Encoding、Accept-Language
extension HTTPHeaders
{
public static let default: HTTPHeaders = [.defaultAcceptEncoding,
.defaultAcceptLanguage,
.defaultUserAgent]
}
public static let defaultAcceptEncoding: HTTPHeader = {
let encodings: [String]
encodings = ["br", "gzip", "deflate"]
return .acceptEncoding(encodings.qualityEncoded())
}()
public static let defaultAcceptLanguage: HTTPHeader =
{
.acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded())
}()
public static let defaultUserAgent: HTTPHeader =
{
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 alamofireVersion = "Alamofire/\(version)"
...
let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
return .userAgent(userAgent)
}
? 運(yùn)行結(jié)果
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
"Accept-Language": "en;q=1.0",
"Host": "httpbin.org",
"User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1",
"X-Amzn-Trace-Id": "Root=1-600e58ba-22848fce5ae105571e598f1e"
},
"origin": "222.76.251.163",
"url": "https://httpbin.org/get"
}
4、ParameterEncoding
https://johnny:p4ssw0rd@www.example.com:443/script.ext;param=value?query=value#ref
==========url的組成=========
scheme //https
user //johnny
password //p4ssw0rd
host //www.example.com
port //443
path // /script.ext
pathExtension //ext
pathComponents //["/", "script.ext"]
parameterString //param=value
query //query=value
fragment //ref
encoding: ParameterEncoding = URLEncoding.default,
如果我有一個(gè)參數(shù)字典,這個(gè)參數(shù)字典又是如何從客戶端傳遞到服務(wù)器的呢?Alamofire中是這樣實(shí)現(xiàn)的:URLEncoding編碼方式會(huì)把參數(shù)直接拼接到URL中或通過(guò)request的httpBody傳值。JSONEncoding編碼方式會(huì)把參數(shù)字典編碼成JSONData后賦值給request的httpBody。
a、ParameterEncoding協(xié)議
-
ParameterEncoding協(xié)議:是一個(gè)定義如何編碼的協(xié)議,常會(huì)用到的
URLEncoding與JSONEncoding編碼方式都遵循這個(gè)協(xié)議 -
encode函數(shù):用來(lái)完成對(duì)傳入的
parameters的編碼工作,把參數(shù)綁定到urlRequest之中 -
urlRequest參數(shù):需要實(shí)現(xiàn)
URLRequestConvertible協(xié)議,實(shí)現(xiàn)該協(xié)議的對(duì)象能夠轉(zhuǎn)換成URLRequest -
parameters參數(shù):類型為
Parameters,也就是字典:public typealias Parameters = [String: Any] -
函數(shù)返回值類型為URLRequest:至于返回的
urlRequest是不是之前的urlRequest,這個(gè)不一定。另一個(gè)比較重要的是該函數(shù)會(huì)拋出異常
public protocol ParameterEncoding
{
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
b、URLEncoding
? url-encoded編碼方式
-
URLEncoding會(huì)生成一個(gè)使用url-encoded方式編碼過(guò)的字符串,可以添加到url或是請(qǐng)求體中,至于使用何種方式取決于編碼的目的地參數(shù) -
http頭中的Content-Type字段會(huì)被設(shè)置為application/x-www-form-urlencoded; charset=utf-8 - 由于沒(méi)有一個(gè)明確的規(guī)定如何編碼一個(gè)集合,我們?cè)谶@里約定,對(duì)于數(shù)組,我們會(huì)在名字后面加上一個(gè)中括號(hào)
[]如(foo[]=1&foo[]=2),對(duì)于字典,則在中括號(hào)中再加入鍵值,如foo[bar]=baz
public struct URLEncoding: ParameterEncoding
? 編碼后的參數(shù)位置:定義編碼后的字符串是放到url還是請(qǐng)求體中
-
methodDependent:對(duì)于
.get、.head、.delete請(qǐng)求,它會(huì)將已編碼查詢字符串應(yīng)用到現(xiàn)有的查詢字符串中;對(duì)于其他類型的請(qǐng)求,會(huì)將其設(shè)置為HTTP body -
queryString: 將編碼字符串設(shè)置或追加到請(qǐng)求的
URL中 -
httpBody:將編碼字符串設(shè)置為
URLRequest的HTTP body
public enum Destination
{
case methodDependent
case queryString
case httpBody
// 是否將編碼字符串放到url中
func encodesParametersInURL(for method: HTTPMethod) -> Bool
{
switch self
{
case .methodDependent: return [.get, .head, .delete].contains(method)
case .queryString: return true
case .httpBody: return false
}
}
}
? ParameterEncoding 協(xié)議的實(shí)現(xiàn):編碼并設(shè)置 request對(duì)象
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
{
// 獲取 request
var urlRequest = try urlRequest.asURLRequest()
// 獲取參數(shù),如果沒(méi)有參數(shù),那么直接返回
guard let parameters = parameters else { return urlRequest }
// 獲取請(qǐng)求方法,同時(shí)根據(jù)請(qǐng)求方法來(lái)判斷是否需要編碼參數(shù)到 url 中
if let method = urlRequest.method, destination.encodesParametersInURL(for: method)// 直接編碼到 url 中
{
// 獲取 url
guard let url = urlRequest.url else
{
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
// 構(gòu)建一個(gè)URLComponents對(duì)象,并在其中添加參數(shù)
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty
{
// 此處 map 是 optional 的map,如果 optionvalue 不為空,則會(huì)調(diào)用 map 內(nèi)的閉包
// 如果 url 中本來(lái)就有一部分參數(shù)了,那么就將新的參數(shù)附加在后面
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
}
else// 這里是要添加到請(qǐng)求體中
{
// 如果請(qǐng)求頭尚未設(shè)置 Content-Type
if urlRequest.headers["Content-Type"] == nil
{
// 在請(qǐng)求頭中設(shè)置編碼格式
urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
}
// 編碼到請(qǐng)求體中
urlRequest.httpBody = Data(query(parameters).utf8)
}
}
? 將參數(shù)編碼為查詢字符串
可以看到URLEncoding方式是將parameters通過(guò)添加 & ,= 的方式拼接到url身后。
private func query(_ parameters: [String: Any]) -> String
{
// 創(chuàng)建一個(gè)數(shù)組,這個(gè)數(shù)組中存放的是元組數(shù)據(jù),元組中存放的是key和字符串類型的value
var components: [(String, String)] = []
// 遍歷參數(shù),對(duì)參數(shù)做進(jìn)一步的處理,然后拼接到數(shù)組中
for key in parameters.keys.sorted(by: <)
{
let value = parameters[key]!
// key的類型是String,但value的類型是any
// 也就是說(shuō)value不一定是字符串,也有可能是數(shù)組或字典,因此針對(duì)value需要做進(jìn)一步的處理
components += queryComponents(fromKey: key, value: value)
}
// 把元組內(nèi)部的數(shù)據(jù)用=號(hào)拼接,然后用符號(hào)&把數(shù)組拼接成字符串
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
? 根據(jù)value類型進(jìn)行百分號(hào)轉(zhuǎn)義
key的類型是String,但value的類型是any,也就是說(shuō)value不一定是字符串,也有可能是數(shù)組或字典,因此針對(duì)value需要做進(jìn)一步的處理。
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)]
{
// 最終結(jié)果
var components: [(String, String)] = []
switch value
{
// 如果value依然是字典,那么鍵后面加上[key]再調(diào)用自身,也就是做遞歸處理
case let dictionary as [String: Any]:
for (nestedKey, value) in dictionary
{
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
// 如果value是數(shù)組,通過(guò)遍歷在鍵后面加上[]后依然調(diào)用自身
// 把數(shù)組拼接到url中的規(guī)則是這樣的:數(shù)組["a", "b", "c"]拼接后的結(jié)果是key[]="a"&key[]="b"&key[]="c"
case let array as [Any]:
for value in array
{
components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
}
// 如果value是NSNumber,要進(jìn)一步判斷這個(gè)NSNumber是不是表示布爾類型
case let number as NSNumber:
if number.isBool// bool 值的處理
{
components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
}
else
{
components.append((escape(key), escape("\(number)")))
}
// 如果value是Bool,轉(zhuǎn)義后直接拼接進(jìn)數(shù)組
case let bool as Bool:
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
// 其他情況,轉(zhuǎn)義后直接拼接進(jìn)數(shù)組
default:
components.append((escape(key), escape("\(value)")))
}
return components
}
? 百分號(hào)轉(zhuǎn)義
上邊函數(shù)中的key已經(jīng)是字符串類型了,那么為什么還要進(jìn)行轉(zhuǎn)義的?這是因?yàn)樵?code>url中有些字符是不允許的,這些字符會(huì)干擾url的解析。:#[]@!$&'()*+,;=這些字符必須要做轉(zhuǎn)義,而?和/可以不用轉(zhuǎn)義。轉(zhuǎn)義的意思就是百分號(hào)編碼。
public func escape(_ string: String) -> String
{
// 使用了系統(tǒng)自帶的函數(shù)來(lái)進(jìn)行百分號(hào)編碼
string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
}
? 進(jìn)行驗(yàn)證
將核心流程范例里的url變?yōu)槭褂?code>URLEncoding對(duì)參數(shù)編碼后的字符串,并且向parameter傳nil,再進(jìn)行請(qǐng)求依然會(huì)得到同樣的結(jié)果。
let url = "http://suggest.taobao.com/sug?code=utf-8&q=%E8%A2%9C%E5%AD%90"
AF.request(url, method: .get, parameters: nil)
輸出結(jié)果為:
json字符串:
{"result":[["襪子女","981907.3641717125"],["襪子男","774851.6019127683"],["襪子女冬","627697.2976415411"],["襪子女中筒襪","350247.61393421463"],["襪子男長(zhǎng)襪","615388.650406337"],["襪子男冬","703565.6863077434"],["襪子女ins潮","285066.1735027441"],["襪子男純棉","303921.3111099697"],["襪子女冬季 加絨","335283.97906813805"],["襪子男中筒","331615.3393111639"]]}
d、JSONParameterEncoder
JSONEncoding的主要作用是把參數(shù)以JSON的形式編碼到request之中,當(dāng)然是通過(guò)request的httpBody進(jìn)行賦值的。JSONEncoding提供了兩種處理函數(shù),一種是對(duì)普通的字典參數(shù)進(jìn)行編碼,另一種是對(duì)字符串?dāng)?shù)組進(jìn)行編碼,處理這兩種情況的函數(shù)基本上是相同的。
? 使用 json 編碼參數(shù)
public struct JSONEncoding: ParameterEncoding
{
// 使用默認(rèn)參數(shù)構(gòu)造
public static var `default`: JSONEncoding { JSONEncoding() }
// 讓其擁有更好的展示效果
public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }
// JSON序列化的寫入方式
public let options: JSONSerialization.WritingOptions
public init(options: JSONSerialization.WritingOptions = [])
{
self.options = options
}
}
? ParameterEncoding 協(xié)議的實(shí)現(xiàn):將parameters轉(zhuǎn)化為二進(jìn)制放入httpBody里
因此也很好理解,為什么get方法的安全性不如post方法,因?yàn)?code>get方法將請(qǐng)求參數(shù)暴露在外,而post放在請(qǐng)求體內(nèi)。
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
{
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do
{
// json 格式化數(shù)據(jù)
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
// 如果 Content-Type 尚未設(shè)置
if urlRequest.headers["Content-Type"] == nil
{
// 設(shè)置請(qǐng)求頭的Content-Type
urlRequest.headers.update(.contentType("application/json"))
}
// 加上請(qǐng)求體
urlRequest.httpBody = data
}
catch
{
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
還有一個(gè)encode方法,基本同上一致,但是可以接受數(shù)組的 json
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest
5、Validation
.validate(statusCode: [200])
a、響應(yīng)碼的認(rèn)證
validate是DataRequest的擴(kuò)展中增加的方法。我們這里先看對(duì)statusCode的認(rèn)證。
extension DataRequest
{
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int
{
validate { [unowned self] _, response, _ in
self.validate(statusCode: acceptableStatusCodes, response: response)
}
}
}
那么到底是如何認(rèn)證的呢?這里我們?cè)龠M(jìn)入它的內(nèi)部實(shí)現(xiàn)。
fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int
{
// 若validate傳入code包含response響應(yīng)的code則認(rèn)證成功
if acceptableStatusCodes.contains(response.statusCode)
{
return .success(())
}
// 若不包括,則返回失敗,拋出AFError
else
{
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(AFError.responseValidationFailed(reason: reason))
}
}
b、錯(cuò)誤驗(yàn)證
acceptableStatusCodes為我們實(shí)際在validate中傳入的statusCode數(shù)組,在例子中為[200]。將例子中的[200]變?yōu)閇201]就會(huì)打印出拋出的錯(cuò)誤,因?yàn)?code>http的正確返回碼為200,我們傳入的數(shù)組不包含200,就會(huì)拋出錯(cuò)誤。當(dāng)然我們也可以在例子中不調(diào)用validate這樣就不會(huì)有這些驗(yàn)證。
錯(cuò)誤信息:responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 200))
c、響應(yīng)內(nèi)容的認(rèn)證
這里只介紹了statusCode響應(yīng)碼的認(rèn)證,實(shí)際上validate里還有對(duì)contentType響應(yīng)內(nèi)容等的認(rèn)證。
public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String
{
validate { [unowned self] _, response, data in
self.validate(contentType: acceptableContentTypes(), response: response, data: data)
}
}
fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
response: HTTPURLResponse,
data: Data?)
-> ValidationResult
where S.Iterator.Element == String
{
guard let data = data, !data.isEmpty else { return .success(()) }
return validate(contentType: acceptableContentTypes, response: response)
}
6、DispatchQueue
a、responseData
我們會(huì)把請(qǐng)求返回的處理放入queue所傳入的隊(duì)列中,若我們沒(méi)有向queue傳入隊(duì)列,那么會(huì)默認(rèn)把處理放入主隊(duì)列中。范例子中我們選擇將處理放入全局隊(duì)列中。
.responseData(queue: DispatchQueue.global())
public func responseData(queue: DispatchQueue = .main...)
b、DispatchQueue+Alamofire.swift
可以順便看一下 DispatchQueue+Alamofire.swift 文件,里面增加了一個(gè)名為after的延遲調(diào)用方法,沒(méi)有添加public關(guān)鍵字,那么說(shuō)明只想在Alamofire內(nèi)部調(diào)用,而不想暴露給我們。
extension DispatchQueue
{
func after(_ delay: TimeInterval, execute closure: @escaping () -> Void)
{
asyncAfter(deadline: .now() + delay, execute: closure)
}
}
c、Request.swift
// 所有內(nèi)部異步操作的串行隊(duì)列
public let underlyingQueue: DispatchQueue
// 用于所有序列化操作的隊(duì)列。默認(rèn)情況下,它是一個(gè)以 underlyingQueue 為目標(biāo)的串行隊(duì)列
public let serializationQueue: DispatchQueue
init(id: UUID = UUID(),
underlyingQueue: DispatchQueue,
serializationQueue: DispatchQueue,
eventMonitor: EventMonitor?,
interceptor: RequestInterceptor?,
delegate: RequestDelegate)
{
self.id = id
self.underlyingQueue = underlyingQueue
self.serializationQueue = serializationQueue
self.eventMonitor = eventMonitor
self.interceptor = interceptor
self.delegate = delegate
}
7、AFError
a、AFDataResponse
.responseData(queue: DispatchQueue.global())
{ (responseData) in
switch responseData.result
{
case .failure(let error):
print("錯(cuò)誤信息:\(error)")
}
}
public func responseData(completionHandler: @escaping (AFDataResponse<Data>) -> Void)
public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
b、AFError的類型
Alamofire里的錯(cuò)誤都已經(jīng)被定義到AFError中,我們打開 AFError.swift。在下面列舉的AFError的類型中,除了invalidURL的參數(shù)是遵循協(xié)議URLConvertible,其余類型的參數(shù)又由枚舉類型組成。這樣就將很多種的錯(cuò)誤類型,包括在了這5種類型中。
//這里為了看得方便,將AFError重新整理了下,源碼中的內(nèi)容要更多,但類型是一樣的
enum AFError
{
case invalidURL(url: URLConvertible) //無(wú)效的URL
case parameterEncodingFailed(reason: ParameterEncodingFailureReason) //請(qǐng)求參數(shù)編碼失敗
case multipartEncodingFailed(reason: MultipartEncodingFailureReason) //多部分編碼失敗
case responseValidationFailed(reason: ResponseValidationFailureReason) //響應(yīng)驗(yàn)證失敗
case responseSerializationFailed(reason: ResponseSerializationFailureReason) //響應(yīng)序列化失敗
}
c、localizedDescription
AFError.swift里面還有很多內(nèi)容,例如對(duì)localizedDescription的實(shí)現(xiàn),在請(qǐng)求拋出錯(cuò)誤時(shí),我們打印error.localizedDescription看到的錯(cuò)誤信息就是在這里定義的。
// 請(qǐng)求參數(shù)編碼失敗
extension AFError.ParameterEncodingFailureReason
{
var localizedDescription: String
{
switch self
{
case .missingURL:
return "URL request to encode was missing a URL"
case let .jsonEncodingFailed(error):
return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
case let .customEncodingFailed(error):
return "Custom parameter encoder failed with error: \(error.localizedDescription)"
}
}
}
8、DataResponse
.responseData(queue: DispatchQueue.global())
{ (responseData) in
a、Any類型
假如說(shuō)序列化后的數(shù)據(jù)是data,最直接的想法就是把data設(shè)置為Any類型,在實(shí)際用到的時(shí)候在進(jìn)行判斷,這也是最普通的一種開發(fā)思維。現(xiàn)在我們就要打破這種思維。我們需要封裝一個(gè)對(duì)象,這個(gè)對(duì)象能夠表達(dá)任何結(jié)果,這就用到了swift中的泛型。
public func responseJSON(queue: DispatchQueue = .main,
dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: @escaping (AFDataResponse<Any>) -> Void) -> Self
b、Result<Any>
上邊的這個(gè)函數(shù)的主要目的是把請(qǐng)求成功后的結(jié)果序列化為JSON。completionHandler函數(shù)的參數(shù)類型為DataResponse<Any>,其中的Any就會(huì)傳遞給Result,也就是Result<Any>。
public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
public struct DataResponse<Success, Failure: Error>
{
public let result: Result<Success, Failure>
}
@frozen public enum Result<Success, Failure> where Failure : Error
{
/// A success, storing a `Success` value.
case success(Success)
/// A failure, storing a `Failure` value.
case failure(Failure)
}
c、jsonObject
那么問(wèn)題來(lái)了,不是把數(shù)據(jù)解析成JSON了嗎?為什么要返回Any類型呢?json本質(zhì)上很類似于JavaScript中的對(duì)象和數(shù)組。JSONSerialization.jsonObject返回的類型是Any,這是因?yàn)榻馕龊蟮臄?shù)據(jù)有可能是數(shù)組,也有可能是字典。當(dāng)然如果不是這兩種格式的數(shù)據(jù),使用JSONSerialization.jsonObject解析會(huì)拋出異常。
// 字典
{
"people":
[
{"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
{"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
{"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
]
}
// 數(shù)組
[
"a",
"b",
"c"
]
d、打印信息
為了能夠打印更加詳細(xì)的信息,又使DataResponse實(shí)現(xiàn)了CustomStringConvertible和CustomDebugStringConvertible協(xié)議。
description
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible
{
public var description: String
{
"\(result)"
}
}
debugDescription
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible
{
public var debugDescription: String
{
guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" }
let requestDescription = DebugDescription.description(of: urlRequest)
let responseDescription = response.map { response in
let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers)
return """
\(DebugDescription.description(of: response))
\(responseBodyDescription.indentingNewlines())
"""
} ?? "[Response]: None"
let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None"
return """
\(requestDescription)
\(responseDescription)
[Network Duration]: \(networkDuration)
[Serialization Duration]: \(serializationDuration)s
[Result]: \(result)
"""
}
}
續(xù)文見(jiàn)下篇:IOS源碼解析:Alamofire 5 功能模塊
Demo
Demo在我的Github上,歡迎下載。
SourceCodeAnalysisDemo