在上一篇文章中,我们简单介绍了NSURLCache的使用方法,但是并没有具体分析磁盘存储的具体细节,今天我们来尝试解析一下实际的存储过程的数据组织细节.
2.1 NSURLCache的存储路径
NSURLCache在使用过过程中,我们大概会有两种方式:
- 使用默认路径:如果没有显式定义NSURLCache或者自定义时没有自定义有效diskPath,系统会默认将缓存保存保存在NSHomeDirectory()目录library/Caches/{bundleid}中;
- 使用自定义路径:如果显式定义了有效的diskPath,系统就会把请求缓存保存在NSHomeDirectory()目录library/Caches/{bundleid}/{diskPath}中.
2.2 NSURLCache数据存储方式
我们以AFN中AFImagedownloader中的自定义diskPath="com.alamofire.imagedownloader"的方式为例,来做一下分析.
2.2.1 NSURLCache的缓存以何种形式存在?
我们在NSHomeDirectory()目录library/Caches/{bundleid}/{diskPath}中查看一下,就会发现有一个Cache.db文件和fsCachedData的文件夹,证明缓存是以数据库的方式进行了保存.
2.2.2 NSURLCache中都保存了哪些信息?
虽然使用Mac自带的sqlite3工具也可以查看sqlite数据库,但是为了更加直观,我们借助一个DB Browser来查看一下数据库中的数据.
我们看到数据库中有五个表,其中
- sqlite_sequence:这个是SQLite的系统表,数据库被创建时,sqlite_sequence表会被自动创建。该表包括两列。第一列为name,用来存储表的名称。第二列为seq,用来保存表对应的RowID的最大值.当对应的表增加记录或者删除,该表对应的记录也会自动更新。如果该值超过最大值,会引起SQL_FULL错误;
- cfurl_cache_schema_version: 用以保存通讯协议版本;
以前两个表信息比较简单,我们不单独做分析.剩余的三张表中,有唯一共有的键entry_ID,所以entry_ID应该是数据库主键,用于连接三张表的信息:
- cfurl_cache_response:表中包含了主键entry_ID,接口版本号version,哈希值hash_value,缓存策略storage_policy,请求链接request_key,请求时间戳time_stamp,以及分区信息partittion.在这张表中,根据request_key,可以获取到唯一对应的主键entry_ID信息;
- cfurl_cache_blob_data:主键entry_ID, 请求对象request_object,响应对象response_object, proto_props(暂时不知道是个啥),用户信息user_info.通过DB Broswer我们卡伊看到,键request_object和response_object对应的类型都是BLOB类型,无法直接查看. 我们尝试将对应的value值以二进制数据导出,然后强制以文本格式打开之后发现他们都是以bplist00字符串开头,查询资料可得这其实是一个plist文件,我们可以使用plutil -convert binary1 -o 命令将二进制数据转化为plist文件.可以看出,这个表中缓存了创建requset_object和resposne_object的必要信息,由cfurl_cache_response中的entry_ID可以在该表中获取到创建对应的请求对象和响应对象的必要信息;
转化之后的plist文件大概这个样子:
plutil -convert binary1 -o request.plist request_object.bin plutil -convert binary1 -o response.plist response_object.bin
- cfrul_cache_receiver_data:主键entry_ID,缓存数据是否在FS上的标志isDataOnFS,存储路径receiver_data.其中isDataOnFS标志用来表示响应对象的缓存的数据是否存储在FS上,那么FS是个什么呢?我们在Cache.db的同层目录下找到了fsCacheData文件夹,而文件下保存的文件命名与表中receiver_data键对应的值一一对应.如果对应的isDataOnFS为真,则键receiver_data中保存是实际缓存数据在fsCacheData中的位置,也就是缓存数据的文件名.根据entry_ID,可以在该表中找到对应的信息行,如果isDataOnFS为真 ,我们可以根据键receiver_data对应的文件夹在fsCacheData中找到真正的缓存数据.
所以根据上边的探索,缓存的查到大概会是这个样子:
- 在cfurl_cache_response中根据request_key(请求接口,即url)查到entry_ID;
- 在cfurl_cache_blob_data根据entry_ID找到response_object;
- 在cfurl_cache_receiver_data中根据entry_ID找到receiver_data.
- 拼接NSCacheURLResponse对象,至此我们就获取到了NSURLCache中的缓存.
NSURLResponse *urlResponse = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[[request allHTTPHeaderFields] objectForKey:@"Accept"] expectedContentLength:[(NSData *)response_object length] textEncodingName:nil]; NSCachedURLResponse *cachedURLResponse = [[NSCachedURLResponse alloc] initWithResponse:urlResponse data:receiver_data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];