YTKNetwork 源码解读 (四) 之 YTKBatchRequest 和 YTKChainRequest

这篇是 YTKNetwork 源码阅读的最后一篇,主要讲解的是 YTKBaseRequest(批量请求),YTKChainRequest(链式请求) 两个部分。

YTKBatchRequest

YTKBatchRequest 用于完成批量请求的任务。当初始化时,需要指定 YTKBatchRequest 的数目,后续无法再添加新的请求。随后按添加顺序依次发起请求,当其中的一个请求失败时判定为 YTKBatchRequest 请求失败,同时 cancel 其它正在执行的请求

协议,用于 YTKBatchRequest 请求失败或者成功的回调

这些是 YTKBatchRequest 的属性,大部分看注释就能理解了。

需要注意的是:

  1. 代理的执行顺序在 block 之前
  2. tag 需要自己手动赋值来区分 YTKBatchRequest,默认值都是 0

初始化方法,在这里你需要指定 YTKRequest 数组。
个人觉得最好将其它默认的初始化方法给禁用掉

设置 YTKBatchRequest 请求成功和失败的回调,当请求结束后,会调用 -clearCompletionBlock 将 block 置为 nil

YTKRequestAccessory 协议用于执行请求不同阶段的回调

请求的开启和取消

请求的数据是否来自于缓存。所有请求中,如果有一个请求的数据来自于缓存,那么这个方法就会返回 YES

初始化方法,将参数 requestArray 赋值给属性 _requestArray,并且会判断里面的元素是否是 YTKRequest 实例,如果不是的话会返回。
初始化内部属性 finishedCount,该属性表示已完成请求的数目

方法开头会判断属性 finishedCount 的值是否大于 0,如果 0 的话则返回。这说明当 YTKBatchRequest 实例创建只能调用 start 方法一次,再次调用的话可能会出现重复开启任务的问题,这里个人还是觉得要进行下判断比较好。
将 YTKRequest 的 delegate 设置为自己,同时清空 YTKRequest 的 block 回调,以确保只有 YTKBatchRequest 处理请求完成后的回调,最后开启请求

stop

触发即将停止回调 toggleAccessoriesWillStopCallBack,并调用 clearRequest 方法循环将 YTKRequest 进行停止操作,随后将自己从 YTKBatchRequestAgent 中移除。

clearRequest

循环调用 YTKRequest 的 stop 方法

设置 YTKBatchRequest 的完成回调,当所有请求都成功调用或者其中一个失败时使用

isDataFromCache

可以看到,只要一个请求的数据来自于缓存,那么方法的返回结果就是 NO

当请求成功后,YTKBatchRequest 在这个方法里面处理数据。

  1. _finishedCount 的值 +1
  2. 如果 _finishedCount 的值等于请求的个数,说明所有的请求都已请求成功,这种情况表示 YTKBatchRequest 请求成功,执行相应的回调

YTKBatchRequest 在这个方法里面处理请求失败的情况。
一个请求失败意味着 YTKBatchRequest 的失败,所以也不用接着处理那些完成的请求。在这里会循环取消 YTKRequest 请求
接着执行失败回调,并将自己从 YTKBatchRequestAgent 移除。

YTKBatchRequestAgent 是一个单例,作用是强引用 YTKBatchRequest,避免被销毁。当任务执行完毕后,将 YTKBatchRequest 移除

小总结

YTKBatchRequest 是一个批量执行请求的类,能够批量执行 YTKRequest 类型的请求。
请求的开启顺序按照任务的添加顺序执行,单个 YTKRequest 完成后,不会执行 YTKRequest 自己的回调 block,代理等回调方法。
当所有请求成功后,执行 YTKBatchRequest 的代理回调,回调 block; 否则执行 YTKBatchRequest 的失败回调

YTKChainRequest

YTKChainRequest 也是批量处理请求的类,只不过是当前面的请求完成后再执行下一个请求。

YTKChainRequest 完成的回调。可以看到在失败回调中多了一个参数 failedBaseRequest

比较简单,看注释就好了。
id<YTKRequestAccessory> 是实现了协议 YTKRequestAccessory 的实例,这个协议里面有请求各种状态的方法,例如将要发起请求,将要结束请求

通过该方法向 YTKChainRequest 里面添加请求,同时设定该请求的完成回调。
我们知道 YTKBaseRequest 可以通过设定属性来设置完成回调,但这里为什么还需要这个方法来设定呢?通过属性设定的回调是否会触发呢?这些在后面会讲到的

一些内部用到的属性

  1. requestArray:请求数组
  2. requestCallbackArray:请求完成回调数组,有的请求没有设定回调,那么会往数组里面添加一个什么都不做的空回调
  3. nextRequestIndex:下一个执行的请求的序号
  4. emptyCallback:空回调

初始化实例,可以看到 _emptyCallback 其实是一个不执行任何操作的 block

开启任务后,通过 startNextRequest 方法开启第一个请求,并将 YTKChainRequest 添加到单例 YTKChainRequestAgent 中,防止被销毁

startNextRequest

该方法用来开启下一个未执行的请求。
_nextRequestIndex 表示该请求在数组中的位置,如果超出了数组范围则返回。
通过 _nextRequestIndex 得到该请求,随后将 delegate 设置为自己,并清除其完成回调,随后开启请求,返回 YES; 否则返回 NO

stop

取消 YTKChainRequest,并将 YTKChainRequest 从单例 YTKChainRequestAgent 中移除。
通过调用 clearRequest 方法取消当前的请求

clearRequest

获取到当前的 YTKBaseRequest,由于 _nextRequestIndex 表示下一个未执行的请求位置,所以这里要 -1。
清空 _requestArray 和 _requestCallbackArray 这两个数组

添加请求及其完成回调。
在 startNextRequest 方法中,我们知道,开启单个任务 YTKBaseRequest 之前,会将 YTKBaseRequest 的 delegate 设置为自己,并清除其回调 block。
所以,这里我们需要通过 _requestCallbackArray 这种方式来额外添加回调
需要注意的是该回调仅在请求成功时被调用

requestFinished

这里是 YTKBaseRequest 的代理方法,当请求成功后调用。
从 _requestCallbackArray 数组中获取该请求的完成回调。当所有的请求都获取成功后,执行 YTKChainRequestAgent 的完成回调

requestFailed

这里是 YTKBaseRequest 的代理方法,当请求失败后调用。需要注意的是,当单个 YTKBaseRequest 请求失败,判断 YTKChainRequestAgent 请求失败,执行其完成回调
如果你想执行 YTKBaseRequest 的失败回调,你可以在代理方法 chainRequestFailed:failedBaseRequest: 中,通过 YTKBaseRequest 得到响应数据,随后执行后续操作

小总结

YTKChainRequest 是一个批量同步执行请求的类,能够批量执行 YTKBaseRequest 类型的请求。
你可以自定义 YTKChainRequest 的实例方法添加 YTKBaseRequest 和 成功回调。

不同于 YTKBatchRequest,YTKChainRequest 中可添加的 request 类型是 YTKBaseRequest 而不是 YTKRequest

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 实现请求的类,它序列化了请求以及响应数据,并且实现了断点下载的功能。

YTKNetwork 源码解读 (二) 之 YTKRequest

YTKCacheMetadata

YTKCacheMetadata 表示缓存元数据,保存了跟缓存相关的一些信息,例如创建时间,创建时的版本号,app 版本号等。
通过比较 YTKCacheMetadata,来判断本地缓存是否有效。如果无效的话则重新发起请求,有效的话则从本地缓存加载 reponse 数据

下面几种比较 YTKCacheMetadata 的情况会被认为本地无效:

  1. version 不匹配
  2. sensitiveDataString 不匹配。不要被它的名字给误导了,其实它就相当于一个额外的一个标识符,自己可以设定,如果 sensitiveDataString 不相等说明不匹配
  3. creationDate 取创建日期到现在的时间戳,如果超过了设定的缓存有效时间说明缓存无效
  4. appVersionString 不匹配,app 版本号不匹配

还有一个属性 NSStringEncoding stringEncoding 用于将本地数据转换成字符串时使用。

另外 YTKCacheMetadata 还支持 NSSecureCoding 协议,协议里的方法就不讲了。

定义了一些跟缓存相关的一些状态码

YTKRequest

YTKRequest 是 YTKBaseRequest 的子类,为 YTKBaseRequest 增加了缓存功能

response 数据是否保存到本地需要满足两个条件:

  1. 当前 response 不是来自于缓存,即 response 需要来自于 request
  2. 缓存有效时间 cacheTimeInSeconds 需要大于 0

-cacheVersion-cacheSensitiveData 这两个标识符都是自己设定的。
保存缓存时,也会将缓存元数据保存在本地,当需要加载缓存时,会先把缓存元数据取出来,比较 cacheVersion,cacheSensitiveData 这两个标识符跟当前的标识符是否相等,如果有不相等说明缓存无效

接下来是 YTKRequest.m 部分

两个宏 NSFoundationVersionNumber_iOS_8_0NSFoundationVersionNumber_With_QoS_Available 用来处理系统在 iOS8 之前和之后两种情况,不过在 iOS11 之后,我们可以使用 if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) 这个关键字来处理系统版本不同的问题

这里创建了一个串行队列,对于 iOS8 之后的系统,还指定了它的队列优先级为 QOS_CLASS_BACKGROUND。需要注意的是 QOS_CLASS_BACKGROUND 的优先级别是最低的,可能是因为不想占用太多系统资源吧。
这个队列用来将 response 保存到本地文件中

上面几种不同类型的缓存对应 YTKResponseSerializerType 的类型

  1. cacheData 和 cacheString 对应于 YTKResponseSerializerTypeHTTP
  2. cacheData 和 cacheJSON 对应于 YTKResponseSerializerTypeJSON
  3. cacheData 和 cacheXML 对应于 YTKResponseSerializerTypeXMLParser

因为 YTKRequest 是 YTKBaseRequest 的子类,在这里覆写了它的 start 方法。

有三种情况会导致不使用缓存数据:

  1. 设置 YTKRequest 的属性 ignoreCache 为 YES,这样会忽略缓存直接进行请求
  2. 如果当前任务是一个下载任务,此时不会对 response 结果进行缓存,自然也就不会有读取缓存这个过程
  3. 加载缓存时出现了问题。例如缓存元数据验证时出现问题,或者是读取缓存文件时出现的问题

如果加载缓存成功,会在主队列分发一个异步任务,用来执行请求完成后的一些方法,这些我们在讲 YTKBaseRequest 的时候提到过了
需要注意的是下面这行代码

1
YTKRequest *strongSelf = self;

这是为了防止执行异步任务的时候 self 突然被释放了。不过我的理解是,既然 GCD 已经强引用了 self,那么在 block 执行完毕之前,self 都是不会被释放掉的,所以这句代码貌似有点多余了

使用 -loadCacheWithError 方法来加载本地缓存,分这么几步:

  1. 判断是否设置了缓存有效时间,如果没有的话说明不使用缓存了,直接返回
  2. 加载缓存元数据,如果元数据不存在,说明缓存也不存在
  3. 验证缓存元数据,判断缓存是否有效
  4. 加载本地缓存

在这里,我们先讲一下第三步验证缓存元数据的方法,加载缓存元数据和加载缓存后面再讲

validateCacheWithError

  1. 首先比较缓存的有效时间,因为缓存元数据中保存了缓存的创建日期,所以这里比较简单
  2. 比较缓存版本号,因为是 long long 类型,所以直接比较就可以
  3. 比较 sensitiveDataString 标识符,因为是字符串类型,所以需要使用 isEqualToString 方法比较。值得注意的是,对于符号 ||,当前面那个条件满足之后就不会再去比较后面那个条件了,所以我们写代码的时候可以把比较简单的判断条件放在前面,同理还有 &&
  4. 比较 app 版本号。这里我觉得只需要使用 dispatch_once 进行一次比较就可以了。如果变 app 版本号变更了,则清除缓存,后面就不再需要比较; 如果版本号没有变更,后面也不需要比较了
1
2
3
4
5
6
// 忽略缓存,开启请求
- (void)startWithoutCache {
// 清空跟缓存相关的属性
[self clearCacheVariables];
[super start];
}

如果不使用缓存,则调用这个方法来开启任务。

清空跟缓存相关属性的值,需要注意的是,并不会清除本地的缓存文件(如果有)

requestCompletePreprocessor 方法在得到请求结果后调用。由于 YTKRequest 覆写了父类 YTKBaseRequest 的 requestCompletePreprocessor 方法,所以首先需要调用父类的 requestCompletePreprocessor 方法。
如果 YTKRequest 的 writeCacheAsynchronously 属性值为 YES,则表示在执行请求回调的线程(一般为主线程),将请求结果保存在本地文件中; 如果值是 NO,则在私有的串行队列 ytkrequest_cache_writing_queue 添加一个异步任务来处理

saveResponseDataToCacheFile

saveResponseDataToCacheFile 方法的作用是将数据保存到本地。

  1. 检查数据是否是来自缓存,如果是的话则返回; 否则执行下一步
  2. 将数据保存到本地,文件名字根据接口名字、参数、请求方式来生成
  3. 生成一个 YTKCacheMetadata 实例,保存缓存元数据,然后使用归档的方式将其保存到本地中,其文件名字为 “缓存名字.metadata”

覆写这些方法,用来控制请求缓存的使用

  • cacheTimeInSeconds:缓存使用时间
  • cacheVersion:缓存版本,充当一个标记
  • cacheSensitiveData:也是一个标记
  • writeCacheAsynchronously:是否异步保存缓存

属性 _dataFromCache 用来标记此时的请求结果是否来自于缓存
根据请求格式 responseSerializerType 的不同,缓存将被转换成的对象也不相同,所以这里有很多成员变量 _cacheData/_cacheXML/_cacheJSON/_cacheString

loadCacheMetadata

加载缓存元数据,使用归档加载,如果加载成功,则该方法返回 YES。
这里需要注意的是 YTKLog 这个宏

YTKLog

展开来其实是一个 C 函数,参数是 format 和可变参数。
方法的实现被包含在了一个 #ifdef/#endif 里面,这样做的好处是在正式版本环境 RELEASE 时,不会再打印 log,减少系统开销

loadCacheData

这个方法的作用是加载本地缓存

首先判断缓存文件是否存在,存在的话则读取数据 data,随后根据属性 responseSerializerType 的值将 data 转换成不同格式的对象

clearCacheVariables

清除跟缓存相关的几个属性,需要注意的是并没有主动的清除本地缓存

createBaseDirectoryAtPath

该方法用来创建文件夹,需要注意的是 createDirectoryAtPath:withIntermediateDirectories:attributes:error:,该方法可以用来创建中间目录。
例如我们创建一个名字为 app/doc/user/info 的文件夹,而当前只存在 app 这个文件夹的话,使用该方法会帮我们创建好 doc,user,info 这几个文件夹

根据 path 来查找是否存在该文件夹,如果不存在的话则使用 createBaseDirectoryAtPath 来创建文件夹; 如果存在的话则判断是否是文件,如果是文件的话则移除该文件,然后使用 createBaseDirectoryAtPath 来创建文件夹

可以看到 createBaseDirectoryAtPath,createBaseDirectoryAtPath 两个方法的使用是为了确保生成指定名字的文件夹

该方法用来创建缓存存放的文件目录

默认的文件夹位置是 /Library/LazyRequestCache,但是我们可以使用 YTKNetworkConfig 的实例方法 addCacheDirPathFilter 来修改文件夹的名字
在确定好文件夹名字之后,使用 createDirectoryIfNeeded 创建该文件夹

该方法用来确定缓存文件的名字

得到请求的 url, basicUrl,参数,然后拼接成一个字符串 requestInfo,随后使用 YTKNetworkUtils 的方法 md5StringFromString,从 requestInfo 中提取 md5,将其作为缓存文件的名字

得到了文件夹名字和文件名字,我们就可以将其拼接成缓存文件的路径啦

总结

YTKRequest 作为 YTKBaseRequest 的子类,为其添加了缓存这个功能。在我们的使用中自定义的 request 子类也是需要直接继承与 YTKRequest 的

YTKNetwork 源码解读 (一) 之 YTKBaseRequest

因为 YTKBaseRequest .h/.m 里代码数目不多,所以这里将从头开始逐行介绍,有一些直接就写在注释里了,有一些需要注意的地方会特别摘出来讲解的。

不知道你有没有注意到 NS_ENUM 后面没有跟着指定一个名字,是的,如果你不需要指定一个类型名字的话,可以直接这样子写。
YTKRequestValidationErrorInvalidStatusCode 值的是 reponse.statusCode 不在 200~299 这个区间内,跟 AFHTTPResponseSerializer 的 acceptableStatusCodes 的范围一致

常见的请求方法

请求的序列化样式,相对于 AFNetworking 少了 AFPropertyListRequestSerializer,可能是因为这种编码方式比较少见吧。

相应的序列化样式,相对于 AFNetworking 少了 AFXMLDocumentResponseSerializer,AFPropertyListResponseSerializer,AFImageResponseSerializer,AFCompoundResponseSerializer 几个类型

对应于 NSURLSessionTask 的 priority 属性,需要在 iOS8 以后的系统中使用,不过一般也不需要再兼容之前的系统了吧。。。

AFMultipartFormData 在 AFNetworking 用于 Content-Type application/form-data 的请求,将数据添加到请求体中。一般用于 upload task 中。
在 AFConstructingBlock 类型的 block 中,你可以将数据添加数据添加到请求体中
在 AFURLSessionTaskProgressBlock 的 block 你可以追踪上传的进度

你可以实现这两个协议,以便在 request 的不同阶段进行相应的处理
需要注意的是这些方法的执行顺序不要搞错了

接下来介绍 YTKBaseRequest 这个类,它是一个抽象类,提供了构建 requeset 时的许多选项

这里大部分的属性是映射 NSHTTPURLResponseNSURLRequestNSURLSessionTask 这几个类的属性的。

需要注意的是 responseObject,如果 resumableDownloadPath 不为空并且 requestTask 是 DownloadTask 类型的,那么这个属性的值就一个文件路径(NSURL),用来保存下载数据的

tag 可以用来标记 YTKBaseRequest,默认值是 0
userInfo 可以用来添加额外信息

requestAccessories 是一个数组,可以用来保存多个实现了 YTKRequestAccessory 协议的对象
constructingBodyBlock 用来将数据添加到请求体中
resumableDownloadPath 是保存下载数据文件的路径,当下载请求失败时,部分的下载数据会自动保存到这个文件中,否则数据会保存到 responseData/responseString 中
resumableDownloadProgressBlock 可以用来追踪下载进度
uploadProgressBlock 可以用来追踪上传进度

-setCompletionBlockWithSuccess:failure: 用来添加请求 成功/失败 的回调
-clearCompletionBlock: 将请求 成功/失败 的回调 block 置为 nil,避免循环引用
-addAccessory: 用来添加实现 YTKRequestAccessory 的对象

  1. -start:开启任务。需要注意是为了让 task 在完成之前不被释放掉,会在这个 task 添加到单例 YTKNetworkAgent 的成员变量 _requestsRecord
  2. -stop:取消任务。我感觉这个方法叫做 cancle 比较合适,我一开始看到 stop 还以为是 suspend 的意思
  3. -startWithCompletionBlockWithSuccess:failure:,设置请求 失败/成功 的回调,并开启任务

这部分是你实现 YTKBaseRequest 子类时可以覆写的方法

-requestCompletePreprocessor,-requestCompleteFilter,-requestFailedPreprocessor,-requestFailedFilter:这几个方法都是对请求 成功/失败 结果的处理,结合之前讲过 YTKRequestDelegateYTKRequestAccessory 两个协议里面的方法,下面给出这些方法的执行顺序.

请求成功后回调的执行顺序:

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

请求失败后回调的执行顺序

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

方法 -cacheFileNameFilterForRequestArgument 用来对请求参数 argument 进行过滤后返回一个新的参数,用在获取缓存文件名字上。

方法 -requestAuthorizationHeaderFieldArray,用于身份验证,在这个方法中你需要返回一个容量为 2 的数组,第一个元素表示账号,第二个元素表示密码。
该认证方式使用用户的 账号/密码 作为凭证信息,进行 base64 编码添加到请求头 Authorization 中传输到服务器中

方法 buildCustomUrlRequest,在这个方法里面你可以放回一个自定义的 request,而不是使用 AFNetworking 的 AFHTTPRequestSerializer 生成。
如果你返回了一个不为 nil 的对象,那么将忽略 requestUrl, requestTimeoutInterval, requestArgument, allowsCellularAccess, requestMethod and requestSerializerType

方法 jsonValidator,在这个方法里面,你可以对 reponse 序列化后的 JSON 对象进行验证。
举个例子,我们要向网址 http://www.yuantiku.com/iphone/users 发送一个 GET 请求,请求参数是 userId 。我们想获得某一个用户的信息,包括他的昵称和等级,我们需要服务器必须返回昵称(字符串类型)和等级信息(数值类型),则可以覆盖 jsonValidator 方法,实现简单的验证。

1
2
3
4
5
6
- (id)jsonValidator {
return @{
@"nick": [NSString class],
@"level": [NSNumber class]
};
}

方法 statusCodeValidator,我觉得如果是验证状态码的话好得加个状态码的参数啊,不过无所谓啦,反正可以自己获取状态码然后再进行判断。该方法返回一个布尔值,如果返回的是 NO 的话将会报错。

接下来讲 .m 文件

在 .h 文件中这些属性都是只读的,在 .m 文件中改成可读写,防止外部修改这些属性的值。

NSURLSessionTaskNSURLResponse 的一些属性映射成自己的属性,便于使用

设置请求 成功/失败 的回调
需要注意的是可以添加多个实现了 YTKRequestAccessory 协议的对象

开启/关闭 请求的方法。
在 start 方法中:

1
2
3
4
5
6
7
8
// 触发 YTKRequestAccessory 代理
- (void)toggleAccessoriesWillStartCallBack {
for (id<YTKRequestAccessory> accessory in self.requestAccessories) {
if ([accessory respondsToSelector:@selector(requestWillStart:)]) {
[accessory requestWillStart:self];
}
}
}

addRequest方法实现

在成功创建 task 后,task 将会被添加到 _requestsRecord 属性中避免被释放。随后调用 resume 开启任务
如果我们没有覆写方法 buildCustomUrlRequest 返回自定义的 request,系统会根据 YTKBaseRequest 创建 request。这部分在 sessionTaskForRequest:error: 中实现

获取 YTKBaseRequest 实例上各种属性的值,例如方法类型,参数,然后在根据 method 的不同,使用不同的方法创建 task。
因为这部分内容都在 YTKNetworkAgent,这里就简单提下。在后面讲解 YTKNetworkAgent 部分的时候再仔细说

stop 方法:

1
2
3
4
5
6
7
8
// 执行代理
[self toggleAccessoriesWillStopCallBack];
// 将 delegate 置为 nil
self.delegate = nil;
// task cancle
[[YTKNetworkAgent sharedAgent] cancelRequest:self];
// 执行代理
[self toggleAccessoriesDidStopCallBack];

如果你的请求是下载任务,并且你指定了一个缓存文件名,那么下载好的部分数据将会写入这个临时文件中,在下次恢复下载时使用。
当然如果要使用断点下载,还需要满足下面的几个条件:

  1. 这个资源自你第一次请求后没有改变
  2. 这个任务是一个 HTTP 或者 HTTP GET 请求
  3. 服务器在 reponse header 提供了 ETag 或者 Last-Modified 字段
  4. 服务器支持字节范围请求
  5. 本地临时文件没有被删除

由于 YTKBaseRequest 是一个基类,所以在这些需要子类覆写的方法里面内容不多

覆写了 -description 方法,方便打印信息