原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、抓包工具
- 1、Charles 抓包工具
- 2、Fiddler Everywhere 抓包工具
- 二、Alamofire 架构
- 1、接口
- 2、请求
- 3、响应
- 4、底层
- 5、其他
- 三、核心流程
- 四、功能点解析
- 1、AF
- 2、Request
- 3、HTTPMethod
- 4、ParameterEncoding
- 5、Validation
- 6、DispatchQueue
- 7、AFError
- 8、DataResponse
- Demo
- 参考文献
一、抓包工具
1、Charles 抓包工具
a、配置各种证书比较麻烦
b、Charles抓包工具总是会疯狂报错(这个问题可以重装Charles抓包工具解决掉)
c、而且还存在抓取的内容乱码显示问题
这个问题解决起来比较麻烦,我差不多都快放弃使用这个工具了,突然却被我解决掉这个问题了,在这里画个圈圈诅咒软件的设计者,你这么复杂的配置让人怎么玩你?
在电脑端安装并信任证书
在Charles
中,设置ssl proxy Setting
中的ssl proxying
的代理网址,按图中填写即可,这一步非常重要,我就是设置了这一步后才没有乱码的
设置抓包的网址和端口,设置为全部都抓
大功告成,终于抓取到正常的内容了,留下了喜极而泣的泪水,没忍住想把创作者拖出去打的冲动。
2、Fiddler Everywhere 抓包工具
推荐使用简单明了的Fiddler Everywhere
抓包工具,这是它的下载地址 Fiddler Everywhere,免费版本的就可以用得很顺溜了。
界面整体看起来也比较干净简洁,而且安装好以后就可以直接使用,不用处理一大堆的配置问题。
和其它抓包工具一样,Fiddler Everywhere
默认也是只能抓取HTTP
请求,需要通过下载证书或进行相关配置,才能正常拦截HTTPS
请求,配置如下,信任证书并勾选捕捉。
二、Alamofire 架构
1、接口
Alamofire.swift:api 声明
// 全局静态变量
public let AF = Session.default
// 当前Alamofire版本
let version = "5.4.1"
2、请求
Request.swift:请求类,用于构建请求
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:参数编码
public protocol ParameterEncoder
open class JSONParameterEncoder: ParameterEncoder
open class URLEncodedFormParameterEncoder: ParameterEncoder
MultipartFormData.swift:自定义表单类
open class MultipartFormData
ServerTrustEvaluation.swift:服务器验证
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、响应
Response.swift:响应类,用于构建响应
public struct DataResponse<Success, Failure: Error>
public struct DownloadResponse<Success, Failure: Error>
Validation.swift:响应数据验证
extension Request
extension DataRequest
extension DataStreamRequest
extension DownloadRequest
Result+Alamofire.swift:请求结果表示
extension Result
AFError.swift:错误类型
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:请求session的管理类,底层使用NSURLSession实现
open class Session
MARK: - DataRequest
MARK: - DataStreamRequest
MARK: - UploadRequest
SessionDelegate.swift:请求Session的代理对象,主要实现NSURLSession的代理方法以及回调闭包
open class SessionDelegate: NSObject
extension SessionDelegate: URLSessionDelegate
extension SessionDelegate: URLSessionTaskDelegate
extension SessionDelegate: URLSessionDataDelegate
extension SessionDelegate: URLSessionDownloadDelegate
DispatchQueue+Alamofire.swift:GCD扩展,增加了一个名为after的延迟调用方法
extension DispatchQueue
5、其他
NetworkReachabilityManager.swift:网络状态监听类
open class NetworkReachabilityManager
Notifications.swift:定义通知
extension Notification
extension NotificationCenter
public final class AlamofireNotifications: EventMonitor
三、核心流程
这里并不会贴上源码里的大段代码,而是通过一个例子来进入到Alamofire
中的各个模块,让读者更好理解。
a、安全认证
在info.plist
中添加App Transport Security Settings
,再将Allow Arbitrary Loads
修改为YES
后控制台输出结果为如下:
b、核心流程的范例
// 淘宝的一个搜索api
let url = "http://suggest.taobao.com/sug"
// 对袜子进行搜索
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("错误信息:\(error)")
}
}
c、范例中的功能点解析
- 通过
Alamofire
的request
开始调用 - 传入
url
- 使用
get
方法 - 传入
parameters
,并在encoding
中定义了参数的编码方式 - 这里并不需要
headers
- 在
validate
中传入需要验证的statusCode
的数组 - 在
responseData
方法里传入全局队列和回调的处理闭包completionHandler
- 在闭包里对返回的数据进行判断,若返回成功,打印
jsonString
,若失败则打印错误
d、输出结果
json字符串:
{"result":[["袜子女","988016.6933754483"],["袜子男","865386.2105082254"],["袜子女冬","662313.8682009963"],["袜子女中筒袜","390508.191495028"],["袜子男长袜","650152.593118986"],["袜子男冬","747552.2895411798"],["袜子女ins潮","286597.0935191857"],["袜子男纯棉","324485.1344208922"],["袜子女冬季 加绒","329439.1491313167"],["袜子男中筒","300330.61181601905"]]}
四、功能点解析
1、AF
a、全局静态变量
AF是最外层用于发起网络请求的静态变量,来自于全局静态变量Session.default
- 降低使用门槛,将复杂的功能实现进行下沉
- 提供单一接口来实现功能上的全面覆盖,没有多余代码,非常简洁
- 方便编程者解读源码,探究SDK的封装思路
public let AF = Session.default
default是提供给全局使用的共享单例
open class Session
{
public static let default = Session()
}
b、请求实际的调用者是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协议
我们在例子里传入的url
是String
类型,也可以将它转成URL
类型再传入,结果都正确。这是因为String
和URL
都遵循URLConvertible
协议,通过实现协议里的asURL()
方法,从String
、URL
或URLComponents
类型转换为URL
,若失败则抛出为AFError
的错误。
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
{
// 如果转换 url 失败, 抛出一个异常
guard let url = url else { throw AFError.invalidURL(url: self) }
return url
}
}
b、URLRequestConvertible协议
负责URLRequest
的转换,和上面类似,就不多介绍了。
// 可以转换成 urlrequest 的协议
public protocol URLRequestConvertible
{
// 返回一个 urlrequest, 如果有错, 可以抛出异常
func asURLRequest() throws -> URLRequest
}
// 创建 urlRequest
extension URLRequestConvertible
{
public var urlRequest: URLRequest? { try? asURLRequest() }
}
// 返回自身
extension URLRequest: URLRequestConvertible
{
public func asURLRequest() throws -> URLRequest { self }
}
c、返回Request的子类
❶ 包括request、download、upload等方法都是传入所需参数,发起请求,再返回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,可以让我们控制请求的暂停,恢复,取消等
请求状态
fileprivate var mutableState = MutableState()
public var state: State { mutableState.state }
public var isInitialized: Bool { state == .initialized }// 初始化
public var isResumed: Bool { state == .resumed }// 恢复请求
public var isSuspended: Bool { state == .suspended }// 暂停请求
public var isCancelled: Bool { state == .cancelled }// 取消请求
public var isFinished: Bool { state == .finished }// 完成请求
暂停请求
public func suspend() -> Self
{
$mutableState.write
{ mutableState in
// 如果不能暂停, 那么就跳过
guard mutableState.state.canTransitionTo(.suspended) else { return }
// 变更可变状态为暂停
mutableState.state = .suspended
// 在下层队列中更新暂停状态,didSuspend()在暂停完成时调用
underlyingQueue.async { self.didSuspend() }
guard let task = mutableState.tasks.last, task.state != .completed else { return }
// 调用真正的网络请求URLSessionTask的suspend()方法
task.suspend()
// 在下层队列中更新暂停状态,didSuspendTask()在URLSessionTask暂停时调用
underlyingQueue.async { self.didSuspendTask(task) }
}
return self
}
取消请求
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
}
恢复请求
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方法,以恢复请求为例。
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是个结构体
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的枚举关联值:\(method.rawValue)")
......
}
输出结果
打印HTTPMethod的值:HTTPMethod(rawValue: "GET")
打印HTTPMethod的原始值:GET
c、会话配置
❶ Session的初始化方法中使用了默认的会话配置
public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default....)
❷ 使用了系统提供的默认会话配置,并为其配置了默认的HTTP的头部信息
public static var default: URLSessionConfiguration
{
let configuration = URLSessionConfiguration.default
configuration.headers = .default
return configuration
}
❸ HTTP默认的头部信息包括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)
}
❹ 运行结果
{
"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,
如果我有一个参数字典,这个参数字典又是如何从客户端传递到服务器的呢?Alamofire
中是这样实现的:URLEncoding
编码方式会把参数直接拼接到URL
中或通过request
的httpBody
传值。JSONEncoding
编码方式会把参数字典编码成JSONData
后赋值给request
的httpBody
。
a、ParameterEncoding协议
-
ParameterEncoding协议:是一个定义如何编码的协议,常会用到的
URLEncoding
与JSONEncoding
编码方式都遵循这个协议 -
encode函数:用来完成对传入的
parameters
的编码工作,把参数绑定到urlRequest
之中 -
urlRequest参数:需要实现
URLRequestConvertible
协议,实现该协议的对象能够转换成URLRequest
-
parameters参数:类型为
Parameters
,也就是字典:public typealias Parameters = [String: Any]
-
函数返回值类型为URLRequest:至于返回的
urlRequest
是不是之前的urlRequest
,这个不一定。另一个比较重要的是该函数会抛出异常
public protocol ParameterEncoding
{
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}
b、URLEncoding
❶ url-encoded编码方式
-
URLEncoding
会生成一个使用url-encoded
方式编码过的字符串,可以添加到url
或是请求体中,至于使用何种方式取决于编码的目的地参数 -
http
头中的Content-Type
字段会被设置为application/x-www-form-urlencoded; charset=utf-8
- 由于没有一个明确的规定如何编码一个集合,我们在这里约定,对于数组,我们会在名字后面加上一个中括号
[]
如(foo[]=1&foo[]=2
),对于字典,则在中括号中再加入键值,如foo[bar]=baz
public struct URLEncoding: ParameterEncoding
❷ 编码后的参数位置:定义编码后的字符串是放到url还是请求体中
-
methodDependent:对于
.get
、.head
、.delete
请求,它会将已编码查询字符串应用到现有的查询字符串中;对于其他类型的请求,会将其设置为HTTP body
-
queryString: 将编码字符串设置或追加到请求的
URL
中 -
httpBody:将编码字符串设置为
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 协议的实现:编码并设置 request对象
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
{
// 获取 request
var urlRequest = try urlRequest.asURLRequest()
// 获取参数,如果没有参数,那么直接返回
guard let parameters = parameters else { return urlRequest }
// 获取请求方法,同时根据请求方法来判断是否需要编码参数到 url 中
if let method = urlRequest.method, destination.encodesParametersInURL(for: method)// 直接编码到 url 中
{
// 获取 url
guard let url = urlRequest.url else
{
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
// 构建一个URLComponents对象,并在其中添加参数
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty
{
// 此处 map 是 optional 的map,如果 optionvalue 不为空,则会调用 map 内的闭包
// 如果 url 中本来就有一部分参数了,那么就将新的参数附加在后面
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
}
else// 这里是要添加到请求体中
{
// 如果请求头尚未设置 Content-Type
if urlRequest.headers["Content-Type"] == nil
{
// 在请求头中设置编码格式
urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
}
// 编码到请求体中
urlRequest.httpBody = Data(query(parameters).utf8)
}
}
❹ 将参数编码为查询字符串
可以看到URLEncoding
方式是将parameters
通过添加 & ,=
的方式拼接到url
身后。
private func query(_ parameters: [String: Any]) -> String
{
// 创建一个数组,这个数组中存放的是元组数据,元组中存放的是key和字符串类型的value
var components: [(String, String)] = []
// 遍历参数,对参数做进一步的处理,然后拼接到数组中
for key in parameters.keys.sorted(by: <)
{
let value = parameters[key]!
// key的类型是String,但value的类型是any
// 也就是说value不一定是字符串,也有可能是数组或字典,因此针对value需要做进一步的处理
components += queryComponents(fromKey: key, value: value)
}
// 把元组内部的数据用=号拼接,然后用符号&把数组拼接成字符串
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
❺ 根据value类型进行百分号转义
key
的类型是String
,但value
的类型是any
,也就是说value
不一定是字符串,也有可能是数组或字典,因此针对value
需要做进一步的处理。
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)]
{
// 最终结果
var components: [(String, String)] = []
switch value
{
// 如果value依然是字典,那么键后面加上[key]再调用自身,也就是做递归处理
case let dictionary as [String: Any]:
for (nestedKey, value) in dictionary
{
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
// 如果value是数组,通过遍历在键后面加上[]后依然调用自身
// 把数组拼接到url中的规则是这样的:数组["a", "b", "c"]拼接后的结果是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,要进一步判断这个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,转义后直接拼接进数组
case let bool as Bool:
components.append((escape(key), escape(boolEncoding.encode(value: bool))))
// 其他情况,转义后直接拼接进数组
default:
components.append((escape(key), escape("\(value)")))
}
return components
}
❻ 百分号转义
上边函数中的key
已经是字符串类型了,那么为什么还要进行转义的?这是因为在url
中有些字符是不允许的,这些字符会干扰url
的解析。:#[]@!$&'()*+,;=
这些字符必须要做转义,而?
和/
可以不用转义。转义的意思就是百分号编码。
public func escape(_ string: String) -> String
{
// 使用了系统自带的函数来进行百分号编码
string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
}
❼ 进行验证
将核心流程范例里的url
变为使用URLEncoding
对参数编码后的字符串,并且向parameter
传nil
,再进行请求依然会得到同样的结果。
let url = "http://suggest.taobao.com/sug?code=utf-8&q=%E8%A2%9C%E5%AD%90"
AF.request(url, method: .get, parameters: nil)
输出结果为:
json字符串:
{"result":[["袜子女","981907.3641717125"],["袜子男","774851.6019127683"],["袜子女冬","627697.2976415411"],["袜子女中筒袜","350247.61393421463"],["袜子男长袜","615388.650406337"],["袜子男冬","703565.6863077434"],["袜子女ins潮","285066.1735027441"],["袜子男纯棉","303921.3111099697"],["袜子女冬季 加绒","335283.97906813805"],["袜子男中筒","331615.3393111639"]]}
d、JSONParameterEncoder
JSONEncoding
的主要作用是把参数以JSON
的形式编码到request
之中,当然是通过request
的httpBody
进行赋值的。JSONEncoding
提供了两种处理函数,一种是对普通的字典参数进行编码,另一种是对字符串数组进行编码,处理这两种情况的函数基本上是相同的。
❶ 使用 json 编码参数
public struct JSONEncoding: ParameterEncoding
{
// 使用默认参数构造
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 协议的实现:将parameters转化为二进制放入httpBody里
因此也很好理解,为什么get
方法的安全性不如post
方法,因为get
方法将请求参数暴露在外,而post
放在请求体内。
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
{
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do
{
// json 格式化数据
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
// 如果 Content-Type 尚未设置
if urlRequest.headers["Content-Type"] == nil
{
// 设置请求头的Content-Type
urlRequest.headers.update(.contentType("application/json"))
}
// 加上请求体
urlRequest.httpBody = data
}
catch
{
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
还有一个encode方法,基本同上一致,但是可以接受数组的 json
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest
5、Validation
.validate(statusCode: [200])
a、响应码的认证
validate
是DataRequest
的扩展中增加的方法。我们这里先看对statusCode
的认证。
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)
}
}
}
那么到底是如何认证的呢?这里我们再进入它的内部实现。
fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int
{
// 若validate传入code包含response响应的code则认证成功
if acceptableStatusCodes.contains(response.statusCode)
{
return .success(())
}
// 若不包括,则返回失败,抛出AFError
else
{
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(AFError.responseValidationFailed(reason: reason))
}
}
b、错误验证
acceptableStatusCodes
为我们实际在validate
中传入的statusCode
数组,在例子中为[200]。将例子中的[200]变为[201]就会打印出抛出的错误,因为http
的正确返回码为200,我们传入的数组不包含200,就会抛出错误。当然我们也可以在例子中不调用validate
这样就不会有这些验证。
错误信息:responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 200))
c、响应内容的认证
这里只介绍了statusCode
响应码的认证,实际上validate
里还有对contentType
响应内容等的认证。
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
我们会把请求返回的处理放入queue
所传入的队列中,若我们没有向queue
传入队列,那么会默认把处理放入主队列中。范例子中我们选择将处理放入全局队列中。
.responseData(queue: DispatchQueue.global())
public func responseData(queue: DispatchQueue = .main...)
b、DispatchQueue+Alamofire.swift
可以顺便看一下 DispatchQueue+Alamofire.swift
文件,里面增加了一个名为after
的延迟调用方法,没有添加public
关键字,那么说明只想在Alamofire
内部调用,而不想暴露给我们。
extension DispatchQueue
{
func after(_ delay: TimeInterval, execute closure: @escaping () -> Void)
{
asyncAfter(deadline: .now() + delay, execute: closure)
}
}
c、Request.swift
// 所有内部异步操作的串行队列
public let underlyingQueue: DispatchQueue
// 用于所有序列化操作的队列。默认情况下,它是一个以 underlyingQueue 为目标的串行队列
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("错误信息:\(error)")
}
}
public func responseData(completionHandler: @escaping (AFDataResponse<Data>) -> Void)
public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
b、AFError的类型
Alamofire
里的错误都已经被定义到AFError
中,我们打开 AFError.swift
。在下面列举的AFError
的类型中,除了invalidURL
的参数是遵循协议URLConvertible
,其余类型的参数又由枚举类型组成。这样就将很多种的错误类型,包括在了这5种类型中。
//这里为了看得方便,将AFError重新整理了下,源码中的内容要更多,但类型是一样的
enum AFError
{
case invalidURL(url: URLConvertible) //无效的URL
case parameterEncodingFailed(reason: ParameterEncodingFailureReason) //请求参数编码失败
case multipartEncodingFailed(reason: MultipartEncodingFailureReason) //多部分编码失败
case responseValidationFailed(reason: ResponseValidationFailureReason) //响应验证失败
case responseSerializationFailed(reason: ResponseSerializationFailureReason) //响应序列化失败
}
c、localizedDescription
AFError.swift
里面还有很多内容,例如对localizedDescription
的实现,在请求抛出错误时,我们打印error.localizedDescription
看到的错误信息就是在这里定义的。
// 请求参数编码失败
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类型
假如说序列化后的数据是data
,最直接的想法就是把data
设置为Any
类型,在实际用到的时候在进行判断,这也是最普通的一种开发思维。现在我们就要打破这种思维。我们需要封装一个对象,这个对象能够表达任何结果,这就用到了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>
上边的这个函数的主要目的是把请求成功后的结果序列化为JSON
。completionHandler
函数的参数类型为DataResponse<Any>
,其中的Any
就会传递给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
那么问题来了,不是把数据解析成JSON
了吗?为什么要返回Any
类型呢?json
本质上很类似于JavaScript
中的对象和数组。JSONSerialization.jsonObject
返回的类型是Any
,这是因为解析后的数据有可能是数组,也有可能是字典。当然如果不是这两种格式的数据,使用JSONSerialization.jsonObject
解析会抛出异常。
// 字典
{
"people":
[
{"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
{"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
{"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
]
}
// 数组
[
"a",
"b",
"c"
]
d、打印信息
为了能够打印更加详细的信息,又使DataResponse
实现了CustomStringConvertible
和CustomDebugStringConvertible
协议。
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)
"""
}
}
续文见下篇:IOS源码解析:Alamofire 5 功能模块
Demo
Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo