YTKNetwork 源码解读 (三) 之 YTKNetworkAgent
这一篇主要用来介绍 YTKNetworkAgent
这个类。当我们生成一个 YTKBaseRequest
实例,使用 -start
方法来发起请求,底层其实是使用 YTKNetworkAgent 来帮助序列化请求 request,以及请求结果 reponse 的序列化和回调。
YTKNetworkConfig
定义了一个叫做 AFURLSessionTaskDidFinishCollectingMetricsBlock 的 block 类型,看过 AF 源码的同学应该知道在 AFURLSessionManager.m 文件中同样定义了这个名字的 block,在同一个文件中 typedef 重复定义貌似不会报错。
NSURLSessionTaskMetrics 是 session 任务指标的封装,每个实例均包含下面几个属性:
- taskInterval:表示任务从创建到完成花费的总时间,任务的创建时间是任务被实例化时的时间;任务完成时间是任务的内部状态将要变为完成的时间
- redirectCount:表示被重定向的次数
- transactionMetrics:包含了任务执行过程中每个请求/响应事务中收集的指标,指标是 NSURLSessionTaskTransactionMetrics 类型
这个 block 用于 NSURLSessionTaskDelegate
协议的回调方法 URLSession:task:didFinishCollectingMetrics:
,简单的,你可以在这个方法中统计网络流量
YTKUrlFilterProtocol 协议里面只有一个方法 - (NSString *)filterUrl:(NSString *)originUrl withRequest:(YTKBaseRequest *)request
,该方法在方法 -buildRequestUrl
中被调用,用来加工 requestUrl
返回的字符串 Url。
YTKNetworkConfig 可以添加多个实现 YTKUrlFilterProtocol 协议的实例
YTKCacheDirPathFilterProtocol 协议里面只有一个方法 - (NSString *)filterCacheDirPath:(NSString *)originPath withRequest:(YTKBaseRequest *)request
,用来加工缓存的默认目录 /Library/LazyRequestCache,YTKNetworkConfig 可以添加多个实现了 YTKCacheDirPathFilterProtocol 的实例
NS_UNAVAILABLE 这个宏的作用是你无法使用这个宏修饰的方法,也就是你只能只用 +sharedConfig
方法来生成实例,也就是单例
- baseUrl: 域名,类似
http://www.yuantiku.com
,默认值为 nil - cdnUrl:cdn url,默认值是 nil
- urlFilters:只读,你可以使用实例方法
-addUrlFilter:
来添加实现了 YTKUrlFilterProtocol 协议的实例,该协议在上面已经提到过了 - cacheDirPathFilters:只读,你可以使用实例方法
-addCacheDirPathFilter:
来添加实现了 YTKCacheDirPathFilterProtocol 协议的实例,该协议在上面已经提到过了 - securityPolicy:安全策略,与证书相关
- debugLogEnabled:是否开启 debug 模式,开启后可打印 log
- sessionConfiguration:session config
- collectingMetricsBlock:收集指标后的回调
这些就不讲了
对应于 .h 文件中的 urlFilters, cacheDirPathFilters 属性,只是一个为只读,而上面的是成员变量可以直接使用
单例的写法,并在 init 进行初始化。
需要注意的是安全策略 securityPolicy 为默认策略,也就是无条件信任服务器的证书
比较简单就不说了
YTKNetworkConfig 是一个单例,你可以用它来:
- 设定域名 baseUrl
- 设定 cdn Url
- 设定安全策略
- 开启 log
- 对接口 url 进行修改
- 对缓存路径进行修改
YTKNetworkAgent
跟上面一样
- addRequest:生成 NSURLSessionDataTask,并开启任务; 通过此方法会添加到一个字典集合中
- cancelRequest:取消任务,并从集合中移除
- cancelAllRequests:取消所有的任务,并从集合中移除
构建请求的 url 字符串
互斥锁,用来保持线程安全。这里使用宏来简化锁的使用
使用方式为:
- 初始化锁:pthread_mutex_init(&_lock, NULL)
- 上锁:pthread_mutex_lock(&_lock)
- 解锁:pthread_mutex_unlock(&_lock)
- 销毁锁,虽然在文件中并没有这步操作:pthread_mutex_destroy(&_lock)
这里是一些 YTKNetworkAgent 的私有成员变量
- _manager:序列化参数,上传的附件,响应结果并发起请求
- _config:网络配置,上一节已经讲过了
- _jsonResponseSerializer:将响应结果序列化成 json
- _xmlParserResponseSerialzier:将响应结果序列化成 xml
- _requestsRecord:task 集合,强引用 task,使其在请求过程中不会被释放
- _processingQueue:并行队列,用来分配请求成功时的回调
- _lock:互斥锁
- _allStatusCodes:状态码集合,用来验证响应结果
生成 YTKNetworkAgent 实例并初始化,值得注意的是这里的状态码集合范围是 (100, 500),不同于 AFN 的 (200, 300)
懒加载 _jsonResponseSerializer、_xmlParserResponseSerialzier 这两个序列化器,它们只有当 responseSerializerType 为 YTKResponseSerializerTypeJSON、YTKResponseSerializerTypeXMLParser 才会被使用
这个方法的作用是构建 request 的 url
先让我们了解一下 URL 的组成,这里举一个例子:http://www.example.com:80/path/to/myfile.html?key1=value1&key2=value2#SomewhereInTheDocument
其中:
- scheme:因特网服务类型,这里是 http
- domain:因特网域名,在这里是 www.example.com
- port:主机上的端口号,默认端口号是 80
- path:服务器上的资源路径,在这里是 /path/to/myfile.html
- parameter:提供给服务器的额外参数,在这里是 ?key1=value1&key2=value2
在这个方法中,如果一个 url 存在 host 跟 scheme,那么就会把它当做一个完整的 url,不需要拼接直接返回
这个方法的作用是创建一个请求序列化器
① 前面部分是根据 requestSerializerType 类型创建不同的 requestSerializer
红色框里面的内容是服务器如果需要 账号/密码 验证时,使用 setAuthorizationHeaderFieldWithUsername:password:
将这些信息添加到请求头中
① 后面部分的内容是设置你自定义的请求头信息
这个方法的作用是创建 NSURLSessionTask task
- 根据参数 YTKBaseRequest 获取到请求 method,url,参数,要上传的文件信息,进度 block以及请求序列化器
- 获取到上面的信息后,根据 method 选择不同的构建方法创建 NSURLSessionTask
当 method 是 GET 时,有两种情况,一种是下载任务,一种是数据任务
这个方法用来构建下载任务。
下载任务有断点下载的作用,所以在这里我们需要判断之前是否下载了部分数据,如果是的话则继续下载,否则的话则重头开始下载。
- 首先我们需要构建 request,这部分由 AFN 的请求序列器 requestSerializer 完成,简单点讲就是设置请求头,设置请求体(将参数编码),创建 NSMutableURLRequest
- 代码块 1 的作用是确保下载路径是一个文件路径,而不是文件夹路径。下载任务完成后,AFN 使用方法
moveItemAtURL:toURL:error:
将文件移动到指定路径,如果此时该路径下已经有一个文件存在,就会失败,所以 YTK 这里在下载前就先判断是否文件,如果存在的话则移除这个文件 - 代码块 2 的作用是恢复断点下载
- 首先获取到之前下载好的数据,这些数据会被保存在 /tmp/Incomplete 文件夹下面
- 获取这些数据,并进行验证。断点数据被保存时会被当做一个 plist 文件,包含了许多的 key 来记录下载信息,所以 YTK 使用
validateResumeData
方法来对这些数据进行验证 - 当断点数据存在并且有效时,使用 AFHTTPSessionManager 的实例方法
downloadTaskWithResumeData:progress:destination:completionHandler
方法来创建一个下载任务 - 到这里如果没有断点下载,则使用 AFHTTPSessionManager 的实例方法
downloadTaskWithRequest:progress:destination:completionHandler
方法重新创建一个下载任务
这个方法的作用是创建数据任务,流程大概就是:
- requestSerializer 生成 NSMutableURLRequest,需要注意的是如果有文件上传的话需要用另一个方法来初始化 request
- 使用 AFHTTPSessionManager 生成 NSURLSessionDataTask,具体实现还是自己翻 AFN 的代码吧
这个方法的作用是生成任务 task,添加到字典集合中强引用避免被销毁,开启任务
- 代码块 1 的作用是初始化 NSURLSessionTask 实例
- 如果我们使用了
-buildCustomUrlRequest
自定义了 NSURLRequest,那么将使用 AFHTTPSessionManager 的方法dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler
来创建 NSURLSessionTask,你对 YTKBaseRequest 一些属性的设置都将失效 - 如果没有自定义 NSURLRequest,那么将使用
sessionTaskForRequest:error:
来创建 NSURLSessionTask,这个方法我们在上面已经讲过了
- 如果我们使用了
- 代码块 2 的作用是在创建 NSURLSessionTask 的过程中失败了,则直接执行失败回调
- 代码块 3 的作用是为 NSURLSessionTask 设置优先级,因为这个属性是 iOS8 之后才有的,所以先用
respondsToSelector:
方法判断了下 - 当 NSURLSessionTask 创建成功后,使用
-addRequestToRecord:
添加到字典集合 _requestsRecord, key/value 对应于 task.taskIdentifier/request - 由于刚创建的 NSURLSessionTask 处于 suspend 暂停状态,所以需要使用 resume 方法来开启
该方法用来取消 request。
当 task 是下载任务时,且 YTKBaseRequest 的 resumableDownloadPath 属性不为 nil,取消任务会将已经下载好的数据保存在本地文件中,以便于下次下载时不重复下载。需要注意时,这些数据不只是你要下载的文件数据,而是一个 plist 文件,里面有许多键值对,例如 NSURLSessionDownloadURL,NSURLSessionResumeBytesReceived,NSURLSessionResumeCurrentRequest
数据保存的文件路径是 /tmp/Incomplete/resumableDownloadPath
,其中 resumableDownloadPath 部分是你自己设定的值
取消任务 task 之后,会将 task 从集合 _requestsRecord 中移除
该方法用来取消所有添加到集合中的任务,值得注意的是锁的使用,这里将锁作用在数据的添加和读取上
该方法用来验证 request 的响应结果,此时请求已经完成,且响应数据已经被序列化赋值给了 YTKBaseRequest 实例。
- 首先验证状态码,有效的状态码范围是 (200,299),在这个范围内说明请求被服务器接收并处理。如果无效则返回
- 如果 YTKBaseRequest 的 responseJSONObject 属性和方法 -jsonValidator 返回值不为 nil,则需要验证 JSON,如果 JSON 中相应字段的值不为 nil,则验证成功; 如果失败则返回
代码块 1:
当我们取消 task 后,底层的 AFN 会立刻调用失败回调,进而调用这个方法。为了防止这种情况,我们在这里做了一个判断,如果 request 为 nil 的时候立即返回
代码块 2:
序列化响应结果,根据 responseSerializerType 属性的不同,将数据序列化成不同类型,比如 JSON Object,NSXMLParser。具体实现过程由 AFN 的响应序列化器实现
- 到这一步如果都没有出现 error,则调用方法
-validateResult:error:
验证响应结果,这个方法我们在上面已经讲过 - 如果验证成功,则调用
-requestDidSucceedWithRequest:
方法处理; 否则,调用-requestDidFailWithRequest:error:
处理,这两个方法我们随后会讲到 - 无论验证成功还是失败,最后都要将 request 从集合 _requestsRecord 中移除,并清空自己的 successCompletionBlock、failureCompletionBlock、uploadProgressBlock
请求成功后的处理方法,会依次调用下面几个方法
- requestCompletePreprocessor:如果使用 cache 的话是在主线程,否则的话在其它线程执行。下面的方法均在主线程执行
- requestWillStop
- requestCompleteFilter
- requestFinished
- successCompletionBlock
- requestDidStop
请求失败的处理方法
代码块 1:
如果此时有断点数据且 resumableDownloadPath 属性不为 nil,则将其保存在本地,用于下载任务
代码块 2:
对于断点下载的任务,后面恢复下载的任务开启时,responseObject 的值会被赋予断点数据的文件路径。
如果下载任务请求失败了,那么将把断点数据读取出来,然后把文件删除,不是很明白为什么把数据读取出来
代码块 3:
执行一些失败的回调,执行顺序如下:
- requestFailedPreprocessor:如果使用 cache 的话是在主线程,否则的话在其它线程执行。下面的方法均在主线程执行
- requestWillStop
- requestFailedFilter
- requestFailed
- failureCompletionBlock
- requestDidStop
总结
YTKNetworkAgent 是 YTK 实现请求的类,它序列化了请求以及响应数据,并且实现了断点下载的功能。
YTKNetwork 源码解读 (三) 之 YTKNetworkAgent
http://example.com/2020/08/06/YTKNetwork-源码解读-三-之-YTKNetworkAgent/