URLCache探索
了解NSURLCahe
NSURLCache類用NSURLRequest對象和NSCachedURLResponse對象的一對一映射關(guān)系實(shí)現(xiàn)了請求數(shù)據(jù)的緩存。它同時(shí)提供內(nèi)存緩存和硬盤緩存,你可以分別自定義內(nèi)存緩存和硬盤緩存的大小,同時(shí)也可以自定義硬盤緩存的目錄。
這是官方文檔對NSURLCache的描述。其中NSURLRequest對象是請求對象,不必多說。NSCachedURLResponse對象是對緩存數(shù)據(jù)的封裝,其中的data屬性是請求回來的JSON(或者其他格式)的二進(jìn)制數(shù)據(jù)。
以下是NSURLCache類提供的方法,基本能夠滿足大多數(shù)的緩存需求。
@interface NSURLCache : NSObject/** 緩存類的單例 */ @property (class, strong) NSURLCache *sharedURLCache; /** 初始化方法 */ - (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(nullable NSString *)path; /** 取得緩存數(shù)據(jù)的方法 */ - (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request; /** 存儲(chǔ)緩存數(shù)據(jù)的方法 */ - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request; /** 刪除指定request的緩存 */ - (void)removeCachedResponseForRequest:(NSURLRequest *)request; /** 刪除全部緩存 */ - (void)removeAllCachedResponses; /** 刪除緩存數(shù)據(jù)的一部分 */ - (void)removeCachedResponsesSinceDate:(NSDate *)date; /** 內(nèi)存緩存的大小 單位:字節(jié) */ @property NSUInteger memoryCapacity; /** 硬盤緩存的大小 單位:字節(jié) */ @property NSUInteger diskCapacity; /** 當(dāng)前可用的內(nèi)存緩存大小 單位:字節(jié) */ @property (readonly) NSUInteger currentMemoryUsage; /** 當(dāng)前可用的硬盤緩存大小 單位:字節(jié) */ @property (readonly) NSUInteger currentDiskUsage; @end緩存工作過程的理解
事實(shí)上,就算什么也不寫,系統(tǒng)也會(huì)根據(jù)默認(rèn)的規(guī)則幫你緩存HTTP請求。但是項(xiàng)目中諸多的邏輯往往并不能讓我們?nèi)绱擞崎e。
此處舉一個(gè)小例子:項(xiàng)目中的請求一般都需要把參數(shù)加密,一般的加密算法,同樣一個(gè)請求,每次加密出來的串都是不一樣的。上面說過,NSURLCache是用NSURLRequest作為Key來實(shí)現(xiàn)緩存的,每次的URL不同導(dǎo)致每次取到的緩存都為空。這時(shí)候就需要做一些事情來保證緩存系統(tǒng)按照我們期望的樣子正常運(yùn)行。
我自己的理解和總結(jié),NSURLCache的工作過程是這樣的:
1.請求前的配置,包括請求頭,響應(yīng)頭,超時(shí)時(shí)間以及緩存策略(后面會(huì)說到有關(guān)緩存策略)。
2.真正去服務(wù)器請求前,判斷緩存策略,調(diào)用cachedResponseForRequest:(NSURLRequest *)request方法試著取緩存或者直接請求網(wǎng)絡(luò)。
3.如果緩存策略允許取緩存,并且取到了緩存,請求成功并且返回緩存數(shù)據(jù)。
4.如果緩存策略允許取緩存,并且沒有取到緩存,再次判斷緩存策略,如果緩存策略允許聯(lián)網(wǎng),則聯(lián)網(wǎng)請求,否則,請求失敗。
5.上述2,3任何一種請求成功的話,判斷緩存策略和服務(wù)器返回的響應(yīng)頭。如果允許存儲(chǔ),則調(diào)用storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request將返回的數(shù)據(jù)存儲(chǔ)到內(nèi)存以及硬盤,否則,直接返回請求成功。
下面是Apple提供的7種緩存策略以及含義:
如果使用了默認(rèn)緩存策略,也就是上面表格中第一個(gè),需要從返回的response的header中獲取相應(yīng)的字段來指導(dǎo)緩存該如何進(jìn)行。
1.Cache-Control字段:常用的有 no-cache,no-store,和max-age。其中no-cache代表不能使用這個(gè)緩存,no-store代表不存儲(chǔ)這個(gè)數(shù)據(jù),max-age代表緩存的有效期(單位為秒)。
2.Expires字段:緩存過期時(shí)間,后面跟一個(gè)日期,此日期之前都可以直接使用本緩存。如果Expires與Cache-Control同時(shí)存在,則Cache-Control優(yōu)先。
3.Last-Modified和If-Modified-Since字段:如果response中有Last-Modified,則在下次請求時(shí),給request的header設(shè)置If-Modified-Since為Last-Modified的值,服務(wù)器校驗(yàn)數(shù)據(jù)是否有變化,如果有變化,返回新數(shù)據(jù),否則,返回304狀態(tài)碼,可以使用此緩存。
4.ETag和If-None-Match字段:如果response中有ETag,則在下次請求時(shí),給request的header設(shè)置If-None-Match為ETag的值,服務(wù)器校驗(yàn)數(shù)據(jù)是否有變化,如果有變化,返回新數(shù)據(jù),否則,返回304狀態(tài)碼,可以使用此緩存。
以上的緩存協(xié)議字段只是我所了解的比較常見的幾種,當(dāng)然HTTP緩存協(xié)議還包括很多很多的內(nèi)容,有興趣的同學(xué)可以自行了解。
Demo應(yīng)用
為了加深理解,我寫了一個(gè)小Demo來探索NSURLCache的運(yùn)行過程。
Demo很簡單,只有WXYRequest請求類,繼承自NSURLCache的自定義WXYURLCache類和發(fā)起請求的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];設(shè)置了20M的內(nèi)存緩存和50M的硬盤緩存。以及自定義的緩存目錄。這是一個(gè)單例,設(shè)置了之后,整個(gè)工程里走系統(tǒng)緩存的請求都會(huì)遵循這個(gè)設(shè)置。此處需要注意一點(diǎn),自定義的目錄只需要設(shè)置一個(gè)目錄名即可,它會(huì)自動(dòng)存到應(yīng)用程序沙盒的Caches目錄下,不需要手動(dòng)獲取Caches目錄。
+ (NSString *)customCachePath{return @"CustomCache"; }第二步、設(shè)置請求,這里使用了AFNetworking。
+ (void)requestWithSuccess:(SuccessBlock)success failure:(failureBlock)failure{AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];//配置請求頭sessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];sessionManager.requestSerializer.cachePolicy = [self getCachePolicy];//緩存策略//配置響應(yīng)頭 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;/** 根據(jù)后臺(tái)返回的響應(yīng)頭來做判斷如何緩存 *///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]; }最后一步、請求數(shù)據(jù)看控制臺(tái)輸出。
這里使用了一個(gè)獲取域名資質(zhì)信息的免費(fèi)接口。
發(fā)起請求,以NSURLRequestReturnCacheDataElseLoad(有緩存取緩存,無緩存請求)緩存策略為例。
/** 發(fā)起請求 */ - (IBAction)WXY_Requet:(id)sender {[WXYRequest requestWithSuccess:^(id response) {} failure:^(NSError *error) { }]; }可以看到控制臺(tái)的輸出是這樣的:
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; }首先根據(jù)緩存策略取緩存,因?yàn)槭堑谝淮握埱?#xff0c;沒有緩存。然后聯(lián)網(wǎng)請求,將請求回來的數(shù)據(jù)存入緩存。最后返回請求成功。通過Charles抓包抓到了一個(gè)HTTP請求。
這時(shí)什么也不做,發(fā)起第二次同樣的的請求,可以看到這時(shí)的控制臺(tái)輸出變成了這樣:
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; }這次取緩存的方法取到了緩存,同樣將數(shù)據(jù)存儲(chǔ)了一次。然后返回請求成功。
通過Charles抓包沒有抓到任何請求。說明這次并沒有聯(lián)網(wǎng)請求,而是根據(jù)緩存策略直接使用的緩存。
把手機(jī)打開飛行模式,再次發(fā)起請求。可以看到控制臺(tái)的輸出和上面是一樣的。說明取緩存的策略下,即使是沒有網(wǎng)絡(luò),也會(huì)返回請求成功。
最后一次實(shí)驗(yàn),飛行模式打開,清除掉緩存。
/** 清空緩存 */ - (IBAction)WXY_RemoveCache:(id)sender {[[NSURLCache sharedURLCache] removeAllCachedResponses]; }這時(shí)發(fā)起請求,可以看到控制臺(tái)的輸出是這樣:
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.首先根據(jù)緩存策略取緩存,取不到緩存,聯(lián)網(wǎng)請求,無網(wǎng)絡(luò)請求失敗之后,并不會(huì)調(diào)用存儲(chǔ)緩存的方法,直接返回請求失敗。
以上只是探討了NSURLRequestReturnCacheDataElseLoad這一種緩存策略的表現(xiàn)情況。其他策略,限于篇幅,不做贅述。有興趣的同學(xué)可以自行試驗(yàn)以加深理解。
寫在最后
因?yàn)樽罱鎿Q掉項(xiàng)目中基于ASI的網(wǎng)絡(luò)框架,改用AFN。在封裝的過程中,順便研究了一下緩存相關(guān),覺得有必要記錄一下,分享給大家,所以寫了這篇文章。
另外,值得注意的是,ASI并不是基于NSURLSession或者NSURLCOnnection的封裝,所以并不會(huì)走NSURLCache的緩存,它有自己的一套緩存系統(tǒng)。只有NSURLSession或者NSURLCOnnection的請求才會(huì)走Apple提供的這個(gè)緩存類。
還有,看了一下緩存的沙盒目錄,NSURLCache通過數(shù)據(jù)庫來實(shí)現(xiàn)存儲(chǔ)緩存。
最后,像前面說的,并不是有了這個(gè)類,就可以不去管緩存的事情了,根據(jù)項(xiàng)目架構(gòu)和需求的不同,在NSURLCache之上需要做的還有很多很多。
?
來自:http://www.cocoachina.com/ios/20170322/18934.html
轉(zhuǎn)載于:https://www.cnblogs.com/fakeCoder/p/5093724.html
總結(jié)
以上是生活随笔為你收集整理的URLCache探索的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2015结束,2016开始
- 下一篇: lamp环境搭建经验总结