原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易,請(qǐng)珍惜蛉谜,之后會(huì)持續(xù)更新稚晚,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈悦陋,這些文章記錄了我的IOS成長(zhǎng)歷程蜈彼,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn)筑辨,大家可通過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è)問題可以重裝Charles抓包工具解決掉)
c、而且還存在抓取的內(nèi)容亂碼顯示問題
這個(gè)問題解決起來比較麻煩,我差不多都快放棄使用這個(gè)工具了诞挨,突然卻被我解決掉這個(gè)問題了,在這里畫個(gè)圈圈詛咒軟件的設(shè)計(jì)者银室,你這么復(fù)雜的配置讓人怎么玩你?
在電腦端安裝并信任證書
在Charles
中,設(shè)置ssl proxy Setting
中的ssl proxying
的代理網(wǎng)址,按圖中填寫即可叠纹,這一步非常重要与涡,我就是設(shè)置了這一步后才沒有亂碼的
設(shè)置抓包的網(wǎng)址和端口,設(shè)置為全部都抓
大功告成,終于抓取到正常的內(nèi)容了恳守,留下了喜極而泣的淚水沥阱,沒忍住想把創(chuàng)作者拖出去打的沖動(dòng)。
2、Fiddler Everywhere 抓包工具
推薦使用簡(jiǎn)單明了的Fiddler Everywhere
抓包工具易茬,這是它的下載地址 Fiddler Everywhere,免費(fèi)版本的就可以用得很順溜了食铐。
界面整體看起來也比較干凈簡(jiǎn)潔寞秃,而且安裝好以后就可以直接使用朗涩,不用處理一大堆的配置問題。
和其它抓包工具一樣,Fiddler Everywhere
默認(rèn)也是只能抓取HTTP
請(qǐng)求渡讼,需要通過下載證書或進(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ì)貼上源碼里的大段代碼,而是通過一個(gè)例子來進(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)解析
- 通過
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)變量,來自于全局靜態(tài)變量Session.default
- 降低使用門檻,將復(fù)雜的功能實(shí)現(xiàn)進(jìn)行下沉
- 提供單一接口來實(shí)現(xiàn)功能上的全面覆蓋疆瑰,沒有多余代碼,非常簡(jiǎn)潔
- 方便編程者解讀源碼,探究SDK的封裝思路
public let AF = Session.default
default是提供給全局使用的共享單例
open class Session
{
public static let default = Session()
}
b掰读、請(qǐng)求實(shí)際的調(diào)用者是URLSession
通過使用 URLSession 來初始化 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] = [])
通過使用URLSessionConfiguration來初始化 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é)議感昼,通過實(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
// 如果不能暫停, 那么就跳過
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
中或通過request
的httpBody
傳值。JSONEncoding
編碼方式會(huì)把參數(shù)字典編碼成JSONData
后賦值給request
的httpBody
功蜓。
a庆冕、ParameterEncoding協(xié)議
-
ParameterEncoding協(xié)議:是一個(gè)定義如何編碼的協(xié)議响巢,常會(huì)用到的
URLEncoding
與JSONEncoding
編碼方式都遵循這個(gè)協(xié)議 -
encode函數(shù):用來完成對(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
方式編碼過的字符串策州,可以添加到url
或是請(qǐng)求體中,至于使用何種方式取決于編碼的目的地參數(shù) -
http
頭中的Content-Type
字段會(huì)被設(shè)置為application/x-www-form-urlencoded; charset=utf-8
- 由于沒有一個(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ù)罪既,如果沒有參數(shù),那么直接返回
guard let parameters = parameters else { return urlRequest }
// 獲取請(qǐng)求方法铡恕,同時(shí)根據(jù)請(qǐng)求方法來判斷是否需要編碼參數(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 中本來就有一部分參數(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
通過添加 & ,=
的方式拼接到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
// 也就是說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
漏健,也就是說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ù)組,通過遍歷在鍵后面加上[]后依然調(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ù)來進(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)然是通過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ì)列中扭仁,若我們沒有向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)用方法瓤帚,沒有添加public
關(guān)鍵字,那么說明只想在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) //無效的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類型
假如說序列化后的數(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
那么問題來了量蕊,不是把數(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ù)文見下篇:IOS源碼解析:Alamofire 5 功能模塊
Demo
Demo在我的Github上舅锄,歡迎下載鞭达。
SourceCodeAnalysisDemo