开发iOS的过程中,有一件非常令人头疼的事,那就是网络请求的调试,无论是后端接口的问题,或是参数结构问题,你总需要一个网络调试的工具来简化调试步骤。
早先很多的网络调试都是通过App外的调试来进行的,这种的好处是可以完全不影响App内的任何逻辑,并且也不用去考虑对网络层可能造成的影响。
map remote
和map local
的功能,可以说是iOS开发中的主流调试工具,但是缺点也很明显,使用时必须保证iPhone和Mac在同一Wi-Fi下,并且使用的时候还需要设置Wi-Fi对应的Proxy,而一旦电脑上的Charles关掉,手机就会连不上网络。在办公室可谓神器,可一旦离开了办公室,就没法使用了。rewrite
以及script
的功能,基本能达到Charles的大部分常用需求,并且可以独立于Mac来进行,不过这种方式也有一定的问题,那就是每次查看网络请求都需要切换App,并且请求是所有应用发出的,而很难只看一个应用的请求。目前GitHub上已经有非常多的网络调试框架,提供了简单的应用内收集网络请求的功能。
URLProtocol
,会使得网络请求的抓取重复。
以上的两大类调试方式,各有优劣,App外调试往往因为并不针对某个应用,导致查询的体验非常一般,而目前GitHub上的调试工具,要么对现有请求造成影响,要么是有其他的体验问题,试想一下,当你的公司同事装了一个tf的版本,发现网络突然连不上了,你基本没有一个非常高效的方法去诊断。为了解决上面的问题,我们决定优化现有的App内调试的方案,下面先介绍一下目前主流的几个网络调试方案的原理。
很多人在入门iOS的时候,都会通过Alamofire
等第三方网络请求库来发送网络请求,但大部分的网络请求库都是基于标准库中URLConnection
或者URLSession
的封装,这两个类一个是旧的封装,而URLSession
则是较新的也是被推荐使用的封装,它们本身对URL的加载、响应等一系列的事件进行了处理,其中就包含了所谓的传输协议的修改,标准库中提供了基础的URL
传输协议,包括http、https、ftp等,同时也提供了自定义传输协议的方式。
标准库对于协议的处理流程也很简单,它提供了一个URLProtocol
的类,并且有一个URLProtocol
子类的数组(这个数组在URLConnection
中是URLProtocol
的类变量,可以通过registerClass
这个类方法来插入,而在新的URLSession
中,则是有对应的configuration来处理),在每次请求发出的时候,系统都会从这个数组中,依次询问是否能处理当前的请求,如果能处理,那么剩下的发送、接收事件都会交由这一个protocol来完成。
因此我们可以通过继承URLProtocol
,并实现相关的方法,作为中间层来处理网络的发送、接收后的处理等,URLProtocol
有能力改变URL
加载过程中的每一个环节,但是又要去调用原始的响应方法,这样的设计让协议的处理不会影响网络调用以及网络响应的调用方式,让网络请求发送方无感知的情况下来做中间的处理。
正是这个类似“隐身”的特点,让URLProtocol
成为了很多网络调试框架使用的首选,他们在URLSession
中的configuration中插入网络调试的Protocol
,那么所有对应的网络请求都会通过这个Protocol
来发送, 在这其中只需要将请求再次发送一遍,就可以在不影响原有请求的情况下,拿到请求的所有回调,并在这其中进行记录。
以上面提到的GodEye 为首的就是这种方法,只不过它内部发送请求用的是老的URLConnection
而不是URLSession
,然而这倒是没有什么影响,这类的实现起来也是基本差不多
URLSession.init(configuration:delegate:delegateQueue:)
方法,然后在调用原初始化方法之前,在URLSessionConfiguration
中插入我们自定义的URLProtocol
,同时调用URLProtocol
下的类方法registerClass
来注册自定义的类。URLProtocol
子类中实现
canInit(with:)
方法,在里面判断这个网络请求是否需要监控,如果不需要可以直接放行canonicalRequest(for:)
方法中,我们通常会对原有的请求进行一些处理,例如加上一个flag将请求标识为已经被处理过了startLoading()
方法中,我们需要将对应的请求发送出去,通常情况下我们会用一个新的URLSession
将请求再次发送,并且将新的delegate设置为自己,这样新的请求的回调就会由当前的URLProtocol
处理stopLoading
方法,我们就负责将发出去的请求停止掉