往期导航:
最近刚换工作,有点忙,没来得及学习。今天有空,随手写点。
简介
之前看的都是按模块功能去分别看的源码实现,其实会比较抽象。学习第三方框架的源码,比较容易的入手方式就是,从最简单的用法开始跑一遍,然后跟着代码里的调用栈去看他的内部实现,配合上代码的调用,能更好的帮助理解框架的原理。
1. 简单get请求html
先来最简单的,发个get请求下百度的首页:
AF.request("http://www.baidu.com").responseString(completionHandler: {
resp in
switch resp.result {
case let .success(str):
debugPrint("request success: \(str)")
case let .failure(err):
debugPrint("request fail: \(err)")
}
})
请求成功会打印出来一大堆文案,就是百度首页的html string,可以这一坨string写到文件,后缀设置为.html,就可以打开查看到百度的首页了。
这里面最外层只有三个东西:
- AF
- request()
- responseString()
1. AF
之前讲过,AF其实就是Alamofire中Session.default的单例,Session是整个Alamofire的核心
// in Alamofire.swift
public let AF = Session.default
2. request()
按住command + ctrl,左键点击request()方法,可以跳到Session中request方法的实现:
ps:把这个request()方法叫做 request()一号 因为Session中定义了很多request方法,不区分一下很容易弄混
//in Session.swift
// 一号方法
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 {
let convertible = RequestConvertible(url: convertible,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers,
requestModifier: requestModifier)
return request(convertible, interceptor: interceptor)
}
可以看到,我们输入的网址string是作为URLConvertible协议类型传入的,该协议可以看作是URL类型的协议抽象,需要实现一个asURL的方法,用来生成请求用的URL,Alamofire内置扩展了String实现了该协议:
// in URLConvertible+URLRequestConvertible.swift
extension String: URLConvertible {
/// Returns a `URL` if `self` can be used to initialize a `URL` instance, otherwise throws.
///
/// - Returns: The `URL` initialized with `self`.
/// - Throws: An `AFError.invalidURL` instance.
public func asURL() throws -> URL {
guard let url = URL(string: self) else { throw AFError.invalidURL(url: self) }
return url
}
}
再逼逼一句:Alamofire中的错误管理都是用的throws来抛出Error。其实习惯了Throws之后,会发现比Result舒服,碰到错误直接抛出就完事儿了。不用像Result那样要写复杂的return类型,不过Result可以明确指定错误类型,throws在catch时,需要对错误类型case处理。throws与Result结合使用就得看不同人的使用习惯了。
继续, 在request()一号方法中,还有其他的6个参数,不过都设置了默认值,可以不用传入,直接用默认值就好,对这些参数有兴趣的话,可以看之前的文章。
在request()一号方法的实现中,使用传入的参数调用RequestEncodableConvertible()方法创建了一个convertible局部变量,类型只是个简单的结构体,实现了URLRequestConvertible协议类型,类似于上面URLConvertible协议类型,是URLRequest类型的协议抽象,需要实现一个asURLRequest()方法,用来生成URLRequest对象。也是内置了一些扩展来实现改协议。
生成convertible局部变量后,return返回,调用了request()二号方法,使用convertible局部变量 + 入参的interceptor拦截器协议对象来继续创建DataRequest。同样跳入该方法,看看实现。此时出问题了,会提示三个同名方法,我们需要判断跳去了哪一个:
判断技巧:
- 首先第二个方法,显示在294行,就是我们当前所在的方法,排除
- 第三个方法,在340行,点过去发现是跟294行方法类似的实现,区别是encoder参数不用,而第一个参数convertible都是URLConvertible协议类型,但是我们准备调用的request()二号方法传入的参数是类型为URLRequestConvertible协议类型,也排除
- 第一个方法点进去看到第一个参数convertible类型为URLRequestConvertible协议类型,ok,这就是正确的request()二号方法
二次逼逼:像这样同名方法在swift中很常见,重灾区是协议方法:某个协议中有一个方法a,然后10个类都实现了改方法,那么当你在某处调用方法a的时候想点击前往实现,你就会看到11个提示。呵呵哒。如果不能缺定调用的是哪个,就会出现修改了实现却不生效的奇怪问题。要找准方法,一方面看代码在调用时,根据调用者类型于入参类型来判断,如果调用者或者入参是协议类型,就GG了,就需要把代码跑起来,在运行到这一行时,看调用者以及参数的具体类型,来判断会前往哪个方法。
看下request()二号方法的实现:
// in Session.swift
open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest {
let request = DataRequest(convertible: convertible,
underlyingQueue: rootQueue,
serializationQueue: serializationQueue,
eventMonitor: eventMonitor,
interceptor: interceptor,
delegate: self)
perform(request)
return request
}
跟request()一号方法有点类似,不过是使用入参的convertible + 拦截器对象,以及,Session所持有的rootQueue(回调执行队列),serializationQueue(响应解析队列),eventMonitor(事件监听器对象),以及把自己作为RequestDelegate对象,构建出了DataRequest对象(这里初始化的方法比较复杂,有兴趣了解的可以看前面的Request相关文章,有介绍初始化的内容),然后执行perform()方法处理创建好的DataRequest对象,处理完成后返回了request对象,注意即便return之后,此时也尚未调用resume()方法来发送请求,请求的发送操作,是在response相关处理中调用的。
cmd+ctrl前往perform()方法,有时候会弹出两个方法,仔细看,另外一个是DispatchWorkItem的方法,估计是xcode抽风了。。。
// in Session.swift
func perform(_ request: Request) {
rootQueue.async {
guard !request.isCancelled else { return }
self.activeRequests.insert(request)
self.requestQueue.async {
// Leaf types must come first, otherwise they will cast as their superclass.
switch request {
case let r as UploadRequest:
self.performUploadRequest(r) // UploadRequest must come before DataRequest due to subtype relationship.
case let r as DataRequest:
self.performDataRequest(r)
case let r as DownloadRequest:
self.performDownloadRequest(r)
case let r as DataStreamRequest: self.performDataStreamRequest(r)
default: fatalError("Attempted to perform unsupported Request subclass: \(type(of: request))")
}
}
}
}
代码稍微有一丢丢多,一行行看,
- 处理request的方法是在rootQueue队列执行,这是个串行队列,Alamofire中内部对request的预处理,大部分都是在该队列执行的。
- 先判断request要是取消了的话,直接return,因为队列是串行的,所以有可能准备处理该Request时,已经被标记为取消了
- Request没被取消的话,把它加入到activeRequests数组中
- 然后派发到requestQueue队列来对不同的Request类型进行特殊处理。这个requestQueue可以由外部指定,如果外部没指定,默认是rootQueue。
我们本次的调用,类型是DataRequest,因此去看下performDataRequest()方法实现:
// in Session.swift
func performDataRequest(_ request: DataRequest) {
dispatchPrecondition(condition: .onQueue(requestQueue))
performSetupOperations(for: request, convertible: request.convertible)
}
非常简单,只是检测下是否在requestQueue执行,然后就继续调用performSetupOperations()方法继续处理,而且把DataRequest中存的URLRequestConvertible类型给取出来了,因为调用的performSetupOperations()方法的第一个参数是Request父类,而convertible参数只是在四个子类中才有。
这里对request进一步处理时,DataRequest跟DataStreamRequest调用的方法一致,DownloadRequest跟UploadRequest需要对上传,下载部分进行一些特殊处理,因此有点不一样。
继续前往performSetupOperations()实现:
// in Session.swift
func performSetupOperations(for request: Request, convertible: URLRequestConvertible) {
dispatchPrecondition(condition: .onQueue(requestQueue))
let initialRequest: URLRequest
do {
initialRequest = try convertible.asURLRequest()
try initialRequest.validate()
} catch {
rootQueue.async { request.didFailToCreateURLRequest(with: error.asAFError(or: .createURLRequestFailed(error: error))) }
return
}
rootQueue.async { request.didCreateInitialURLRequest(initialRequest) }
guard !request.isCancelled else { return }
guard let adapter = adapter(for: request) else {
rootQueue.async { self.didCreateURLRequest(initialRequest, for: request) }
return
}
adapter.adapt(initialRequest, for: self) { result in
do {
let adaptedRequest = try result.get()
try adaptedRequest.validate()
self.rootQueue.async {
request.didAdaptInitialRequest(initialRequest, to: adaptedRequest)
self.didCreateURLRequest(adaptedRequest, for: request)
}
} catch {
self.rootQueue.async { request.didFailToAdaptURLRequest(initialRequest, withError: .requestAdaptationFailed(error: error)) }
}
}
}
有点长,具体讲解在前面Session相关文章里。这里简单总结下:
- 构建原始URLRequest对象a
- 对a进行有效行校验
- 告知Request,a已经初始化完成,把a传给Request进行内部处理
- 如果Request中有请求适配器,逐一调用适配器对a进行处理
- 处理完成之后,告知Request已经适配完成
- 最终调用didCreateURLRequest()方法,performSetupOperations()方法完成
然后继续前往didCreateURLRequest()方法:
// in Session.swift
func didCreateURLRequest(_ urlRequest: URLRequest, for request: Request) {
dispatchPrecondition(condition: .onQueue(rootQueue))
request.didCreateURLRequest(urlRequest)
guard !request.isCancelled else { return }
let task = request.task(for: urlRequest, using: session)
requestTaskMap[request] = task
request.didCreateTask(task)
updateStatesForTask(task, request: request)
}
这里的核心操作:调用request的task(for:,using:)方法创建了URLSessionTask,这就是真正用来发送请求的task,然后把这个task存在了Session的requestTaskMap字典中,key是request,这样可以在任意时候根据request找到它所对应的task。
然后调用updateStatesForTask()方法,来根据task的状态更新request的状态,该方法在多个地方均有调用,在这里的调用,因为是新创建的task,其实没有做任何事情,直接break结束了。
注意:虽然这个时候创建了URLSessionTask,但是此时其实并没有调用task的resume()方法。也就是说,此时请求还未发送,发送请求的任务时在某个request第一次调用response方法的时候执行的。而且如果关闭了Session的startRequestsImmediately属性,就需要对Request对象手动调用resume()方法,才会开始发送请求。
以上,AF对请求的处理完成,已经创建好用来发送请求的URLSessionTask,并且创建好了与之对应的Request对象,而且把Request返回了,接下来我们就可以对Request对象进行各种调用,包括validate()有效性检测等方法,也可以开始调用response()方法来开始发送请求了,本次例子中我们用的是用String格式来接受响应。
3. responseString()
上面创建好了Request,准备发请求,就是从这个方法开始,个人感觉这里面对于响应的处理还是比较抽象的,因为每次调用response不是简单的对task调用resume()发出去,而是使用了一个个的闭包,把响应相关的逻辑包在闭包里存起来,然后在适当的时候调用这些闭包,同时还会出现,一边调用一边继续追加的情况,简直离谱。。。
一切的开始:先从responseString()方法起。。。注意这里进入改方法实现时也会有两个实现,另外一个时DownloadRequest的实现,不要弄错了。
// in ResponseSerialization.swift
@discardableResult
public func responseString(queue: DispatchQueue = .main,
dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
encoding: String.Encoding? = nil,
emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
completionHandler: @escaping (AFDataResponse<String>) -> Void) -> Self {
response(queue: queue,
responseSerializer: StringResponseSerializer(dataPreprocessor: dataPreprocessor,
encoding: encoding,
emptyResponseCodes: emptyResponseCodes,
emptyRequestMethods: emptyRequestMethods),
completionHandler: completionHandler)
}
可以看到,也是有一大堆参数,但都有默认值,包括:回调调用队列(主队列),data预处理器(字符串编码预处理器),文本编码等,唯一需要必须传入的参数就是完成的回调闭包。
注意这个方法返回的类型是Self,也就意味着,你可以继续对它进行response调用,一个Request可以调用无数次response方法,当你看明白response()方法的原理之后你就明白为什么可以这样了。
方法实现很简单,只是进一步调用了一个response()方法,把需要的参数透传了过去。继续进入这个方法,同样注意DownloadRequest下同名方法的干扰。
核心来了!!!这个方法,包含了对请求完成后的数据进行处理的逻辑,但是这个逻辑很蛋疼,并不是立刻执行的,而是把所有的逻辑写好,放到闭包里,存在Request中,等到请求完成之后,再调用这个必报,所以在闭包的实现里可以看到,直接通过self.response来获取响应头,用self.data来取得响应数据,用self.metrics来获取本次请求的指标。
首次看这里的实现可能会疑惑:这时候请求不是没发出去么?这三个可选类型的值不应该都是nil么?没错!在刚进入这个response方法的时候,这三个属性的值都是nil,但是在下面的一大堆的处理逻辑,其实是在一个闭包里的,真正执行这些方法的时机,其实是请求完成了,因此这个时候,这三个属性都是有值了的。在这里并不详细介绍怎么去处理响应,有兴趣的话可以看前面响应与解析文章。
这里重点说下这个方法的设计理念--如何实现链式调用
// in ResponseSerialization.swift
@discardableResult
public func response<Serializer: DataResponseSerializerProtocol>(queue: DispatchQueue = .main,
responseSerializer: Serializer,
completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>) -> Void)
-> Self {
appendResponseSerializer {
//。。。。。
//一大堆的处理逻辑
}
return self
}
上面说了,创建好Request之后,responseString方法返回的类型是Self,可以连续链式的调用responseString。所以看上面这个response方法的简化实现,返回也是Self,方法简化之后,就会发现,只是简单调用了appendResponseSerializer方法传入了一个很大的闭包,就return self了,由于这里尾随闭包的写法,导致乍一看不容易看明白,但是你只要看明白这个方法是appendResponseSerializer()方法接受了一个很大一坨闭包参数,然后去看这个方法内部实现,就能理解了。
前往appendResponseSerializer()方法:
// in Request.swift
func appendResponseSerializer(_ closure: @escaping () -> Void) {
$mutableState.write { mutableState in
//加入数组
mutableState.responseSerializers.append(closure)
//如果请求完成了,标记为继续
if mutableState.state == .finished {
mutableState.state = .resumed
}
//如果响应解析完成了, 调一下解析方法
if mutableState.responseSerializerProcessingFinished {
underlyingQueue.async { self.processNextResponseSerializer() }
}
//如果不是完成状态, 且可以转换为继续状态
if mutableState.state.canTransitionTo(.resumed) {
//问问Session是否需要立刻开始, 是的话就立刻调用resume()
underlyingQueue.async { if self.delegate?.startImmediately == true { self.resume() } }
}
}
}
ps:逼逼一下:这里的mutableState只是把一些需要线程安全的属性包裹成了一个struct而已
看到了么,方法开始调用时,并没有立刻执行闭包,而是把闭包给存到了responseSerializers数组中,根据Request当前状态来延迟调用:
- 如果当前Request被标记finished,表示这个Request已经请求完成了,现在是正在对他再一次执行response,那就把request标记成resumed,表示正在执行,后续逻辑就不会发送请求,而是直接调用入解析回调。
- 检测下responseSerializerProcessingFinished字段,该字段是用来标记当前正在执行的某个解析回调是否完成了,如果完成了,就手动调用一下processNextResponseSerializer()方法来执行下一个回调闭包。如果尚未完成,就不需要手动调用。因为解析完成的逻辑会自己检测执行下一个解析。递归的来着。。。
- 最后,需要判断下,当前request能否变成resume,这个操作是因为:只有initialized,suspended,这两个状态,可以变成resume,代表需要对task调用resume方法来发送请求/继续请求。而且,必须request的startImmediately设置为true才会自动调用resume()方法,否则,需要手动调用resume()才能发送请求。
这里就回答了上面的问题:创建完Request之后是什么时候调用resume()方法来发送请求的。
2. 请求JSON
原理差不多。。。有空再补
3. 上传请求
同上
总结
总结下整个流程:
- 创建Session用来发送请求,可以自定义创建,也可以直接使用AF单例,默认的Session来发送请求
- 外部调用request()方法,传入URLConvertible,创建Request对象
- 内部开始先用URLConvertible来创建URLRequestConvertible对象
- 用URLRequestConvertible创建Request(这个Request就是返回给外部的对象),保存到Session中,然后开始对Request进行预处理
- 先创建初始URLRequest,用预处理器对其进行预处理,在预处理前后都有使用方法来告知Request,流程变更,用来通知事件监听器,所以看EventMonitor协议中,有一大堆生命周期的回调事件。
- 预处理完成,返回Request对象
- 外部调用response系列方法,在这些方法中实现对响应的处理
- 内部会先创建对原始响应数据的处理闭包
- 先解析响应(还记录了解析所花的时间)
- 解析失败的重试
- 解析成功执行外部传入的完成回调
- 把这个闭包追加保存到Request的responseSerializers数组中
- 检测下当前Request是不是已经完成了,完成的话重新标记成执行中
- 检测下当前Request是否已经完成了全部响应的解析,如果是的话,就手动执行processNextResponseSerializer()方法开始继续执行responseSerializers数组中的解析闭包
- 检测下当前Request是否需要发送请求,需要的话,就调用resume()方法发送请求
- 内部会先创建对原始响应数据的处理闭包
正是由于第三步对response方法的调用逻辑,所以我们可以在创建好一个Request之后,不停对它进行response串行调用。虽然请求只会发送一次,但是却可以对响应数据进行多次解析,每次解析均独立。
可以单独调用,也可以链式调用:
let stringHandle: (AFDataResponse<String>) -> Void = {
resp in
//这是string解析
}
let jsonHandle: (AFDataResponse<Any>) -> Void = {
resp in
//这是json解析
}
let req = AF.request("http://*****")
req.responseJSON(completionHandler: jsonHandle)
req.responseString(completionHandler: stringHandle).responseJSON(completionHandler: jsonHandle)
req.responseString(completionHandler: stringHandle)
快乐的一批~
补充
明白response的处理原理之后,其他类似的对request的validate()校验等处理,也都是类似的原理,通过创建闭包,捕捉变量,然后把闭包保存起来,到何时的时候再执行,思路很完美,不过实现实在不好看懂。
这其实也是函数式编程的思维:使用闭包捕捉局部变量,把闭包延迟执行,来延长局部变量的生命周期。这样的思维也是RxSwift这个牛逼框架的基础,但是真的是太抽象了。太难理解了。
体现在代码上就是:
- 普通写代码:写了逻辑,跑起来到这里了,就逐行执行,顺畅,舒服~~
- 函数式编程:写了一坨逻辑,包进闭包,跑起来到这里了,哎,把这个闭包存起来,不执行,等到后面某个时刻了,想起来我还有个存着的闭包呢。拿出来执行一下,debug起来会非常难受,逐行调试会发现,下一步代码直接跳过了一大堆,直接函数return了???然后在往下执行,某个时刻突然又从一个闭包数组里取出来执行了,断点又跳回去刚才那一坨了。。。阿西吧。。。
以上均为个人理解~难免有错,如有错误,欢迎评论指出~非常感谢~