YTKNetwork 源码解读 (二) 之 YTKRequest
YTKCacheMetadata
YTKCacheMetadata 表示缓存元数据,保存了跟缓存相关的一些信息,例如创建时间,创建时的版本号,app 版本号等。
通过比较 YTKCacheMetadata,来判断本地缓存是否有效。如果无效的话则重新发起请求,有效的话则从本地缓存加载 reponse 数据
下面几种比较 YTKCacheMetadata 的情况会被认为本地无效:
- version 不匹配
- sensitiveDataString 不匹配。不要被它的名字给误导了,其实它就相当于一个额外的一个标识符,自己可以设定,如果 sensitiveDataString 不相等说明不匹配
- creationDate 取创建日期到现在的时间戳,如果超过了设定的缓存有效时间说明缓存无效
- appVersionString 不匹配,app 版本号不匹配
还有一个属性 NSStringEncoding stringEncoding
用于将本地数据转换成字符串时使用。
另外 YTKCacheMetadata 还支持 NSSecureCoding 协议,协议里的方法就不讲了。
定义了一些跟缓存相关的一些状态码
YTKRequest
YTKRequest 是 YTKBaseRequest 的子类,为 YTKBaseRequest 增加了缓存功能
response 数据是否保存到本地需要满足两个条件:
- 当前 response 不是来自于缓存,即 response 需要来自于 request
- 缓存有效时间 cacheTimeInSeconds 需要大于 0
-cacheVersion
,-cacheSensitiveData
这两个标识符都是自己设定的。
保存缓存时,也会将缓存元数据保存在本地,当需要加载缓存时,会先把缓存元数据取出来,比较 cacheVersion,cacheSensitiveData 这两个标识符跟当前的标识符是否相等,如果有不相等说明缓存无效
接下来是 YTKRequest.m
部分
两个宏 NSFoundationVersionNumber_iOS_8_0
和 NSFoundationVersionNumber_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 的类型
- cacheData 和 cacheString 对应于 YTKResponseSerializerTypeHTTP
- cacheData 和 cacheJSON 对应于 YTKResponseSerializerTypeJSON
- cacheData 和 cacheXML 对应于 YTKResponseSerializerTypeXMLParser
因为 YTKRequest 是 YTKBaseRequest 的子类,在这里覆写了它的 start
方法。
有三种情况会导致不使用缓存数据:
- 设置 YTKRequest 的属性 ignoreCache 为 YES,这样会忽略缓存直接进行请求
- 如果当前任务是一个下载任务,此时不会对 response 结果进行缓存,自然也就不会有读取缓存这个过程
- 加载缓存时出现了问题。例如缓存元数据验证时出现问题,或者是读取缓存文件时出现的问题
如果加载缓存成功,会在主队列分发一个异步任务,用来执行请求完成后的一些方法,这些我们在讲 YTKBaseRequest 的时候提到过了
需要注意的是下面这行代码
1 | YTKRequest *strongSelf = self; |
这是为了防止执行异步任务的时候 self 突然被释放了。不过我的理解是,既然 GCD 已经强引用了 self,那么在 block 执行完毕之前,self 都是不会被释放掉的,所以这句代码貌似有点多余了
使用 -loadCacheWithError 方法来加载本地缓存,分这么几步:
- 判断是否设置了缓存有效时间,如果没有的话说明不使用缓存了,直接返回
- 加载缓存元数据,如果元数据不存在,说明缓存也不存在
- 验证缓存元数据,判断缓存是否有效
- 加载本地缓存
在这里,我们先讲一下第三步验证缓存元数据的方法,加载缓存元数据和加载缓存后面再讲
- 首先比较缓存的有效时间,因为缓存元数据中保存了缓存的创建日期,所以这里比较简单
- 比较缓存版本号,因为是 long long 类型,所以直接比较就可以
- 比较 sensitiveDataString 标识符,因为是字符串类型,所以需要使用
isEqualToString
方法比较。值得注意的是,对于符号||
,当前面那个条件满足之后就不会再去比较后面那个条件了,所以我们写代码的时候可以把比较简单的判断条件放在前面,同理还有&&
- 比较 app 版本号。这里我觉得只需要使用 dispatch_once 进行一次比较就可以了。如果变 app 版本号变更了,则清除缓存,后面就不再需要比较; 如果版本号没有变更,后面也不需要比较了
1 | // 忽略缓存,开启请求 |
如果不使用缓存,则调用这个方法来开启任务。
清空跟缓存相关属性的值,需要注意的是,并不会清除本地的缓存文件(如果有)
requestCompletePreprocessor
方法在得到请求结果后调用。由于 YTKRequest 覆写了父类 YTKBaseRequest 的 requestCompletePreprocessor 方法,所以首先需要调用父类的 requestCompletePreprocessor 方法。
如果 YTKRequest 的 writeCacheAsynchronously
属性值为 YES,则表示在执行请求回调的线程(一般为主线程),将请求结果保存在本地文件中; 如果值是 NO,则在私有的串行队列 ytkrequest_cache_writing_queue
添加一个异步任务来处理
saveResponseDataToCacheFile
方法的作用是将数据保存到本地。
- 检查数据是否是来自缓存,如果是的话则返回; 否则执行下一步
- 将数据保存到本地,文件名字根据接口名字、参数、请求方式来生成
- 生成一个 YTKCacheMetadata 实例,保存缓存元数据,然后使用归档的方式将其保存到本地中,其文件名字为 “缓存名字.metadata”
覆写这些方法,用来控制请求缓存的使用
- cacheTimeInSeconds:缓存使用时间
- cacheVersion:缓存版本,充当一个标记
- cacheSensitiveData:也是一个标记
- writeCacheAsynchronously:是否异步保存缓存
属性 _dataFromCache 用来标记此时的请求结果是否来自于缓存
根据请求格式 responseSerializerType 的不同,缓存将被转换成的对象也不相同,所以这里有很多成员变量 _cacheData/_cacheXML/_cacheJSON/_cacheString
加载缓存元数据,使用归档加载,如果加载成功,则该方法返回 YES。
这里需要注意的是 YTKLog 这个宏
展开来其实是一个 C 函数,参数是 format 和可变参数。
方法的实现被包含在了一个 #ifdef/#endif 里面,这样做的好处是在正式版本环境 RELEASE 时,不会再打印 log,减少系统开销
这个方法的作用是加载本地缓存
首先判断缓存文件是否存在,存在的话则读取数据 data,随后根据属性 responseSerializerType 的值将 data 转换成不同格式的对象
清除跟缓存相关的几个属性,需要注意的是并没有主动的清除本地缓存
该方法用来创建文件夹,需要注意的是 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 源码解读 (二) 之 YTKRequest
http://example.com/2020/08/03/YTKNetwork-源码解读-二-之-YTKRequest/