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

YTKUrlFilterProtocol 协议里面只有一个方法 - (NSString *)filterUrl:(NSString *)originUrl withRequest:(YTKBaseRequest *)request,该方法在方法 -buildRequestUrl 中被调用,用来加工 requestUrl 返回的字符串 Url。
YTKNetworkConfig 可以添加多个实现 YTKUrlFilterProtocol 协议的实例

YTKCacheDirPathFilterProtocol

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 是一个单例,你可以用它来:

  1. 设定域名 baseUrl
  2. 设定 cdn Url
  3. 设定安全策略
  4. 开启 log
  5. 对接口 url 进行修改
  6. 对缓存路径进行修改

YTKNetworkAgent

跟上面一样

  • addRequest:生成 NSURLSessionDataTask,并开启任务; 通过此方法会添加到一个字典集合中
  • cancelRequest:取消任务,并从集合中移除
  • cancelAllRequests:取消所有的任务,并从集合中移除

buildRequestUrl

构建请求的 url 字符串

互斥锁,用来保持线程安全。这里使用宏来简化锁的使用
使用方式为:

  1. 初始化锁:pthread_mutex_init(&_lock, NULL)
  2. 上锁:pthread_mutex_lock(&_lock)
  3. 解锁:pthread_mutex_unlock(&_lock)
  4. 销毁锁,虽然在文件中并没有这步操作:pthread_mutex_destroy(&_lock)

这里是一些 YTKNetworkAgent 的私有成员变量

  1. _manager:序列化参数,上传的附件,响应结果并发起请求
  2. _config:网络配置,上一节已经讲过了
  3. _jsonResponseSerializer:将响应结果序列化成 json
  4. _xmlParserResponseSerialzier:将响应结果序列化成 xml
  5. _requestsRecord:task 集合,强引用 task,使其在请求过程中不会被释放
  6. _processingQueue:并行队列,用来分配请求成功时的回调
  7. _lock:互斥锁
  8. _allStatusCodes:状态码集合,用来验证响应结果

生成 YTKNetworkAgent 实例并初始化,值得注意的是这里的状态码集合范围是 (100, 500),不同于 AFN 的 (200, 300)

懒加载 _jsonResponseSerializer、_xmlParserResponseSerialzier 这两个序列化器,它们只有当 responseSerializerType 为 YTKResponseSerializerTypeJSON、YTKResponseSerializerTypeXMLParser 才会被使用

buildRequestUrl

这个方法的作用是构建 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

  1. 根据参数 YTKBaseRequest 获取到请求 method,url,参数,要上传的文件信息,进度 block以及请求序列化器
  2. 获取到上面的信息后,根据 method 选择不同的构建方法创建 NSURLSessionTask

当 method 是 GET 时,有两种情况,一种是下载任务,一种是数据任务

构建下载任务

这个方法用来构建下载任务。
下载任务有断点下载的作用,所以在这里我们需要判断之前是否下载了部分数据,如果是的话则继续下载,否则的话则重头开始下载。

  1. 首先我们需要构建 request,这部分由 AFN 的请求序列器 requestSerializer 完成,简单点讲就是设置请求头,设置请求体(将参数编码),创建 NSMutableURLRequest
  2. 代码块 1 的作用是确保下载路径是一个文件路径,而不是文件夹路径。下载任务完成后,AFN 使用方法 moveItemAtURL:toURL:error: 将文件移动到指定路径,如果此时该路径下已经有一个文件存在,就会失败,所以 YTK 这里在下载前就先判断是否文件,如果存在的话则移除这个文件
  3. 代码块 2 的作用是恢复断点下载
    • 首先获取到之前下载好的数据,这些数据会被保存在 /tmp/Incomplete 文件夹下面
    • 获取这些数据,并进行验证。断点数据被保存时会被当做一个 plist 文件,包含了许多的 key 来记录下载信息,所以 YTK 使用 validateResumeData 方法来对这些数据进行验证
    • 当断点数据存在并且有效时,使用 AFHTTPSessionManager 的实例方法 downloadTaskWithResumeData:progress:destination:completionHandler 方法来创建一个下载任务
    • 到这里如果没有断点下载,则使用 AFHTTPSessionManager 的实例方法 downloadTaskWithRequest:progress:destination:completionHandler 方法重新创建一个下载任务

这个方法的作用是创建数据任务,流程大概就是:

  1. requestSerializer 生成 NSMutableURLRequest,需要注意的是如果有文件上传的话需要用另一个方法来初始化 request
  2. 使用 AFHTTPSessionManager 生成 NSURLSessionDataTask,具体实现还是自己翻 AFN 的代码吧

这个方法的作用是生成任务 task,添加到字典集合中强引用避免被销毁,开启任务

  1. 代码块 1 的作用是初始化 NSURLSessionTask 实例
    • 如果我们使用了 -buildCustomUrlRequest 自定义了 NSURLRequest,那么将使用 AFHTTPSessionManager 的方法 dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler 来创建 NSURLSessionTask,你对 YTKBaseRequest 一些属性的设置都将失效
    • 如果没有自定义 NSURLRequest,那么将使用 sessionTaskForRequest:error: 来创建 NSURLSessionTask,这个方法我们在上面已经讲过了
  2. 代码块 2 的作用是在创建 NSURLSessionTask 的过程中失败了,则直接执行失败回调
  3. 代码块 3 的作用是为 NSURLSessionTask 设置优先级,因为这个属性是 iOS8 之后才有的,所以先用 respondsToSelector: 方法判断了下
  4. 当 NSURLSessionTask 创建成功后,使用 -addRequestToRecord: 添加到字典集合 _requestsRecord, key/value 对应于 task.taskIdentifier/request
  5. 由于刚创建的 NSURLSessionTask 处于 suspend 暂停状态,所以需要使用 resume 方法来开启

cancelRequest

该方法用来取消 request。

当 task 是下载任务时,且 YTKBaseRequest 的 resumableDownloadPath 属性不为 nil,取消任务会将已经下载好的数据保存在本地文件中,以便于下次下载时不重复下载。需要注意时,这些数据不只是你要下载的文件数据,而是一个 plist 文件,里面有许多键值对,例如 NSURLSessionDownloadURL,NSURLSessionResumeBytesReceived,NSURLSessionResumeCurrentRequest
数据保存的文件路径是 /tmp/Incomplete/resumableDownloadPath,其中 resumableDownloadPath 部分是你自己设定的值

取消任务 task 之后,会将 task 从集合 _requestsRecord 中移除

cancelAllRequests

该方法用来取消所有添加到集合中的任务,值得注意的是锁的使用,这里将锁作用在数据的添加和读取上

该方法用来验证 request 的响应结果,此时请求已经完成,且响应数据已经被序列化赋值给了 YTKBaseRequest 实例。

  1. 首先验证状态码,有效的状态码范围是 (200,299),在这个范围内说明请求被服务器接收并处理。如果无效则返回
  2. 如果 YTKBaseRequest 的 responseJSONObject 属性和方法 -jsonValidator 返回值不为 nil,则需要验证 JSON,如果 JSON 中相应字段的值不为 nil,则验证成功; 如果失败则返回

代码块 1:
当我们取消 task 后,底层的 AFN 会立刻调用失败回调,进而调用这个方法。为了防止这种情况,我们在这里做了一个判断,如果 request 为 nil 的时候立即返回

代码块 2:
序列化响应结果,根据 responseSerializerType 属性的不同,将数据序列化成不同类型,比如 JSON Object,NSXMLParser。具体实现过程由 AFN 的响应序列化器实现

  1. 到这一步如果都没有出现 error,则调用方法 -validateResult:error: 验证响应结果,这个方法我们在上面已经讲过
  2. 如果验证成功,则调用 -requestDidSucceedWithRequest: 方法处理; 否则,调用 -requestDidFailWithRequest:error: 处理,这两个方法我们随后会讲到
  3. 无论验证成功还是失败,最后都要将 request 从集合 _requestsRecord 中移除,并清空自己的 successCompletionBlock、failureCompletionBlock、uploadProgressBlock

请求成功后的处理方法,会依次调用下面几个方法

  1. requestCompletePreprocessor:如果使用 cache 的话是在主线程,否则的话在其它线程执行。下面的方法均在主线程执行
  2. requestWillStop
  3. requestCompleteFilter
  4. requestFinished
  5. successCompletionBlock
  6. requestDidStop

请求失败的处理方法

代码块 1:
如果此时有断点数据且 resumableDownloadPath 属性不为 nil,则将其保存在本地,用于下载任务

代码块 2:
对于断点下载的任务,后面恢复下载的任务开启时,responseObject 的值会被赋予断点数据的文件路径。
如果下载任务请求失败了,那么将把断点数据读取出来,然后把文件删除,不是很明白为什么把数据读取出来

代码块 3:
执行一些失败的回调,执行顺序如下:

  1. requestFailedPreprocessor:如果使用 cache 的话是在主线程,否则的话在其它线程执行。下面的方法均在主线程执行
  2. requestWillStop
  3. requestFailedFilter
  4. requestFailed
  5. failureCompletionBlock
  6. requestDidStop

总结

YTKNetworkAgent 是 YTK 实现请求的类,它序列化了请求以及响应数据,并且实现了断点下载的功能。

作者

千行

发布于

2020-08-06

更新于

2022-10-21

许可协议

评论