iOS~URLCache探索

iOS~URLCache探索

一个随时需要进行HTTP请求的完善的iOS应用,为了流畅的体验,用户流量的节省,缓存是不得不考虑的需求。值得庆幸的是,Apple已经为开发者们做好了这一切,接下来,就一起研究一下一个被很多开发者忽略的类:NSURLCache。

了解NSURLCahe

NSURLCache类用NSURLRequest对象和NSCachedURLResponse对象的一对一映射关系实现了请求数据的缓存。它同时提供内存缓存和硬盘缓存,你可以分别自定义内存缓存和硬盘缓存的大小,同时也可以自定义硬盘缓存的目录。

这是官方文档对NSURLCache的描述。其中NSURLRequest对象是请求对象,不必多说。NSCachedURLResponse对象是对缓存数据的封装,其中的data属性是请求回来的JSON(或者其他格式)的二进制数据。

以下是NSURLCache类提供的方法,基本能够满足大多数的缓存需求。

@interface NSURLCache : NSObject

/** 缓存类的单例 */

@property (class, strong) NSURLCache *sharedURLCache;

/** 初始化方法 */

- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(nullable NSString *)path;

/** 取得缓存数据的方法 */

- (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;

/** 存储缓存数据的方法 */

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;

/** 删除指定request的缓存 */

- (void)removeCachedResponseForRequest:(NSURLRequest *)request;

/** 删除全部缓存 */

- (void)removeAllCachedResponses;

/** 删除缓存数据的一部分 */

- (void)removeCachedResponsesSinceDate:(NSDate *)date;

/** 内存缓存的大小 单位:字节 */

@property NSUInteger memoryCapacity;

/** 硬盘缓存的大小 单位:字节 */

@property NSUInteger diskCapacity;

/** 当前可用的内存缓存大小 单位:字节 */

@property (readonly) NSUInteger currentMemoryUsage;

/** 当前可用的硬盘缓存大小 单位:字节 */

@property (readonly) NSUInteger currentDiskUsage;

@end

缓存工作过程的理解

事实上,就算什么也不写,系统也会根据默认的规则帮你缓存HTTP请求。但是项目中诸多的逻辑往往并不能让我们如此悠闲。

此处举一个小例子:项目中的请求一般都需要把参数加密,一般的加密算法,同样一个请求,每次加密出来的串都是不一样的。上面说过,NSURLCache是用NSURLRequest作为Key来实现缓存的,每次的URL不同导致每次取到的缓存都为空。这时候就需要做一些事情来保证缓存系统按照我们期望的样子正常运行。

我自己的理解和总结,NSURLCache的工作过程是这样的:

1.请求前的配置,包括请求头,响应头,超时时间以及缓存策略(后面会说到有关缓存策略)。

2.真正去服务器请求前,判断缓存策略,调用cachedResponseForRequest:(NSURLRequest *)request方法试着取缓存或者直接请求网络。

3.如果缓存策略允许取缓存,并且取到了缓存,请求成功并且返回缓存数据。

4.如果缓存策略允许取缓存,并且没有取到缓存,再次判断缓存策略,如果缓存策略允许联网,则联网请求,否则,请求失败。

5.上述2,3任何一种请求成功的话,判断缓存策略和服务器返回的响应头。如果允许存储,则调用storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request将返回的数据存储到内存以及硬盘,否则,直接返回请求成功。

下面是Apple提供的7种缓存策略以及含义:

如果使用了默认缓存策略,也就是上面表格中第一个,需要从返回的response的header中获取相应的字段来指导缓存该如何进行。

1.Cache-Control字段:常用的有 no-cache,no-store,和max-age。其中no-cache代表不能使用这个缓存,no-store代表不存储这个数据,max-age代表缓存的有效期(单位为秒)。

2.Expires字段:缓存过期时间,后面跟一个日期,此日期之前都可以直接使用本缓存。如果Expires与Cache-Control同时存在,则Cache-Control优先。

3.Last-Modified和If-Modified-Since字段:如果response中有Last-Modified,则在下次请求时,给request的header设置If-Modified-Since为Last-Modified的值,服务器校验数据是否有变化,如果有变化,返回新数据,否则,返回304状态码,可以使用此缓存。

4.ETag和If-None-Match字段:如果response中有ETag,则在下次请求时,给request的header设置If-None-Match为ETag的值,服务器校验数据是否有变化,如果有变化,返回新数据,否则,返回304状态码,可以使用此缓存。

以上的缓存协议字段只是我所了解的比较常见的几种,当然HTTP缓存协议还包括很多很多的内容,有兴趣的同学可以自行了解。

Demo应用

为了加深理解,我写了一个小Demo来探索NSURLCache的运行过程。

Demo很简单,只有WXYRequest请求类,继承自NSURLCache的自定义WXYURLCache类和发起请求的ViewController控制器。

第一步、配置自定义缓存类。

//配置缓存

NSUInteger memoryCapacity = 20*1024*1024;

NSUInteger diskCapacity = 50*1024*1024;

WXYURLCache *customURLCache = [[WXYURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:[WXYURLCache customCachePath]];

[NSURLCache setSharedURLCache:customURLCache];

设置了20M的内存缓存和50M的硬盘缓存。以及自定义的缓存目录。这是一个单例,设置了之后,整个工程里走系统缓存的请求都会遵循这个设置。此处需要注意一点,自定义的目录只需要设置一个目录名即可,它会自动存到应用程序沙盒的Caches目录下,不需要手动获取Caches目录。

+ (NSString *)customCachePath{

return @"CustomCache";

}

第二步、设置请求,这里使用了AFNetworking。

+ (void)requestWithSuccess:(SuccessBlock)success failure:(failureBlock)failure{

AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];

//配置请求头

sessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];

sessionManager.requestSerializer.cachePolicy = [self getCachePolicy];//缓存策略

//配置响应头

sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];

[sessionManager GET:customURLString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

NSLog(@"\n请求成功:\n \nURL:%@\n  \nresponse:%@\n\n", task.currentRequest.URL.absoluteString, responseObject);

success(responseObject);

} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

NSLog(@"\n请求失败: \n \nURL:%@\n  \nerror:%@\n\n", task.currentRequest.URL.absoluteString, [error.userInfo objectForKey:@"NSLocalizedDescription"]);

failure(error);

}];

}

其中的缓存策略,每种都试了一遍。

+ (NSURLRequestCachePolicy)getCachePolicy{

NSURLRequestCachePolicy cachePolicy;

/** 根据后台返回的响应头来做判断如何缓存 */

//cachePolicy = NSURLRequestUseProtocolCachePolicy;

/** 每次刷新,不取缓存 */

//cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;

/** 有缓存取缓存,无缓存请求 */

cachePolicy = NSURLRequestReturnCacheDataElseLoad;

/** 有缓存取缓存,无缓存返回失败 */

//cachePolicy = NSURLRequestReturnCacheDataDontLoad;

return cachePolicy;

}

第三步、重写NSURLCache的方法。

重写取缓存方法。

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request{

NSCachedURLResponse *cachedURLResponse = [super cachedResponseForRequest:request];

id cacheData = nil;

if (!cachedURLResponse.data) {

cacheData = @"取到的缓存为空";

}

else{

cacheData = [NSJSONSerialization JSONObjectWithData:cachedURLResponse.data options:NSJSONReadingMutableContainers error:nil];

}

NSLog(@"\n取缓存:\n  \nURL:%@\n  \nresponse:%@\n\n", request.URL.absoluteString, cacheData);

return cachedURLResponse;

}

重写存缓存方法。

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request{

id cacheData = [NSJSONSerialization JSONObjectWithData:cachedResponse.data options:NSJSONReadingMutableContainers error:nil];

NSLog(@"\n存缓存:\n  \nURL:%@\n  \nresponse:%@\n\n", request.URL.absoluteString, cacheData);

[super storeCachedResponse:cachedResponse forRequest:request];

}

最后一步、请求数据看控制台输出。

这里使用了一个获取域名资质信息的免费接口。

#define customURLString @"http://www.sojson.com/api/beian/baidu.com"

发起请求,以NSURLRequestReturnCacheDataElseLoad(有缓存取缓存,无缓存请求)缓存策略为例。

/** 发起请求 */

- (IBAction)WXY_Requet:(id)sender {

[WXYRequest requestWithSuccess:^(id response) {

} failure:^(NSError *error) {

}];

}

可以看到控制台的输出是这样的:

2017-03-20 23:12:23.425763 WXYURLChache[530:99758]

取缓存:

URL:http://www.sojson.com/api/beian/baidu.com

response:取到的缓存为空

2017-03-20 23:12:24.094012 WXYURLChache[530:99758]

存缓存:

URL:http://www.sojson.com/api/beian/baidu.com

response:{

checkDate = "";

domain = " baidu.com ";

icp = "\U4eacICP\U8bc1030173\U53f7";

indexUrl = "www.baidu.com";

name = "\U5317\U4eac\U767e\U5ea6\U7f51\U8baf\U79d1\U6280\U6709\U9650\U516c\U53f8";

nature = "\U4f01\U4e1a";

nowIcp = "\U4eacICP\U8bc1030173\U53f7-1";

search = "baidu.com";

sitename = "\U767e\U5ea6";

type = 200;

}

2017-03-20 23:12:24.095622 WXYURLChache[530:99560]

请求成功:

URL:http://www.sojson.com/api/beian/baidu.com

response:{

checkDate = "";

domain = " baidu.com ";

icp = "\U4eacICP\U8bc1030173\U53f7";

indexUrl = "www.baidu.com";

name = "\U5317\U4eac\U767e\U5ea6\U7f51\U8baf\U79d1\U6280\U6709\U9650\U516c\U53f8";

nature = "\U4f01\U4e1a";

nowIcp = "\U4eacICP\U8bc1030173\U53f7-1";

search = "baidu.com";

sitename = "\U767e\U5ea6";

type = 200;

}

首先根据缓存策略取缓存,因为是第一次请求,没有缓存。然后联网请求,将请求回来的数据存入缓存。最后返回请求成功。通过Charles抓包抓到了一个HTTP请求。

这时什么也不做,发起第二次同样的的请求,可以看到这时的控制台输出变成了这样:

2017-03-20 23:19:29.117268 WXYURLChache[530:99619]

取缓存:

URL:http://www.sojson.com/api/beian/baidu.com

response:{

checkDate = "";

domain = " baidu.com ";

icp = "\U4eacICP\U8bc1030173\U53f7";

indexUrl = "www.baidu.com";

name = "\U5317\U4eac\U767e\U5ea6\U7f51\U8baf\U79d1\U6280\U6709\U9650\U516c\U53f8";

nature = "\U4f01\U4e1a";

nowIcp = "\U4eacICP\U8bc1030173\U53f7-1";

search = "baidu.com";

sitename = "\U767e\U5ea6";

type = 200;

}

2017-03-20 23:19:29.120207 WXYURLChache[530:100693]

存缓存:

URL:http://www.sojson.com/api/beian/baidu.com

response:{

checkDate = "";

domain = " baidu.com ";

icp = "\U4eacICP\U8bc1030173\U53f7";

indexUrl = "www.baidu.com";

name = "\U5317\U4eac\U767e\U5ea6\U7f51\U8baf\U79d1\U6280\U6709\U9650\U516c\U53f8";

nature = "\U4f01\U4e1a";

nowIcp = "\U4eacICP\U8bc1030173\U53f7-1";

search = "baidu.com";

sitename = "\U767e\U5ea6";

type = 200;

}

2017-03-20 23:19:29.123088 WXYURLChache[530:99560]

请求成功:

URL:http://www.sojson.com/api/beian/baidu.com

response:{

checkDate = "";

domain = " baidu.com ";

icp = "\U4eacICP\U8bc1030173\U53f7";

indexUrl = "www.baidu.com";

name = "\U5317\U4eac\U767e\U5ea6\U7f51\U8baf\U79d1\U6280\U6709\U9650\U516c\U53f8";

nature = "\U4f01\U4e1a";

nowIcp = "\U4eacICP\U8bc1030173\U53f7-1";

search = "baidu.com";

sitename = "\U767e\U5ea6";

type = 200;

}

这次取缓存的方法取到了缓存,同样将数据存储了一次。然后返回请求成功。

通过Charles抓包没有抓到任何请求。说明这次并没有联网请求,而是根据缓存策略直接使用的缓存。

把手机打开飞行模式,再次发起请求。可以看到控制台的输出和上面是一样的。说明取缓存的策略下,即使是没有网络,也会返回请求成功。

最后一次实验,飞行模式打开,清除掉缓存。

/** 清空缓存 */

- (IBAction)WXY_RemoveCache:(id)sender {

[[NSURLCache sharedURLCache] removeAllCachedResponses];

}

这时发起请求,可以看到控制台的输出是这样:

2017-03-20 23:28:37.618673 WXYURLChache[530:101997]

取缓存:

URL:http://www.sojson.com/api/beian/baidu.com

response:取到的缓存为空

2017-03-20 23:28:37.646091 WXYURLChache[530:99560]

请求失败:

URL:http://www.sojson.com/api/beian/baidu.com

error:The Internet connection appears to be offline.

首先根据缓存策略取缓存,取不到缓存,联网请求,无网络请求失败之后,并不会调用存储缓存的方法,直接返回请求失败。

以上只是探讨了NSURLRequestReturnCacheDataElseLoad这一种缓存策略的表现情况。其他策略,限于篇幅,不做赘述。有兴趣的同学可以自行试验以加深理解。

写在最后

因为最近要替换掉项目中基于ASI的网络框架,改用AFN。在封装的过程中,顺便研究了一下缓存相关,觉得有必要记录一下,分享给大家,所以写了这篇文章。

另外,值得注意的是,ASI并不是基于NSURLSession或者NSURLCOnnection的封装,所以并不会走NSURLCache的缓存,它有自己的一套缓存系统。只有NSURLSession或者NSURLCOnnection的请求才会走Apple提供的这个缓存类。

还有,看了一下缓存的沙盒目录,NSURLCache通过数据库来实现存储缓存。

最后,像前面说的,并不是有了这个类,就可以不去管缓存的事情了,根据项目架构和需求的不同,在NSURLCache之上需要做的还有很多很多。

来自:http://www.cocoachina.com/ios/20170322/18934.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容