? ? ?最近作者做的項(xiàng)目中需要用到UIWebView的離線緩存功能,本來(lái)滿(mǎn)心歡喜的想著在UIWebView的代理方法中看看有沒(méi)有什么代理方法可以直接做到緩存的功能,結(jié)果還是太天真了,后來(lái)網(wǎng)上搜索了一下(主要參考了在code4app上面rusking作業(yè)對(duì)UIWebView離線瀏覽的代碼實(shí)現(xiàn)(地址https://github.com/lzhlewis2015/UIWebViewLocalCache),研究的過(guò)程中也花了不少時(shí)間,所以想在這里把我的心得分享一下),發(fā)現(xiàn)可以使用NSURLCache這個(gè)類(lèi)實(shí)現(xiàn)。原理就是大多數(shù)的網(wǎng)絡(luò)請(qǐng)求都會(huì)先調(diào)用這個(gè)類(lèi)中的- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request 這個(gè)方法,那我們只要重寫(xiě)這個(gè)類(lèi),就能達(dá)到本地緩存的目的了。
下面是大致的邏輯
1 判斷請(qǐng)求中的request 是不是使用get方法,據(jù)資料顯示一些本地請(qǐng)求的協(xié)議也會(huì)進(jìn)到這個(gè)方法里面來(lái),所以在第一部,要把不相關(guān)的請(qǐng)求排除掉。
2 判斷緩存文件夾里面是否存在該文件,如果存在,繼續(xù)判斷文件是否過(guò)期,如果過(guò)期,則刪除。如果文件沒(méi)有過(guò)期,則提取文件,然后組成NSCacheURLResponse返回到方法當(dāng)中。
3在有網(wǎng)絡(luò)的情況下,如果文件夾中不存在該文件,則利用NSConnection這個(gè)類(lèi)發(fā)網(wǎng)絡(luò)請(qǐng)求,再把返回的data和response 數(shù)據(jù)本地化存儲(chǔ)起來(lái),然后組成NSCacheURLResponse返回到方法當(dāng)中。
4其中BaseTools和其他沒(méi)有在本.m文件中定義的類(lèi)為常用的工具類(lèi),這里不一一展開(kāi)了。
大致邏輯就這么多,話(huà)不多說(shuō),直接看代碼實(shí)現(xiàn)(關(guān)鍵代碼有注釋?zhuān)?/p>
#import "CustomURLCache.h"
#import "NSObject+Network.h"
#import "BaseTools.h"
@interface CustomURLCache(private)
- (NSString *)cacheFolder;
- (NSString *)cacheFilePath:(NSString *)file;
- (NSString *)cacheRequestFileName:(NSString *)requestUrl;
- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl;
- (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request;
- (void)deleteCacheFolder;
@end
@implementation CustomURLCache
- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime {
if (self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) {
//cacheTime 為你所希望本地緩存的時(shí)間(以秒計(jì)算,如果設(shè)為60,則60秒之后本地緩存文件過(guò)期)
self.cacheTime = cacheTime;
if (path)
self.diskPath = path;
else ? ?
self.diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
self.responseDictionary = [NSMutableDictionary dictionaryWithCapacity:0];
}
return self;
}//
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
// 這里判斷如果請(qǐng)求方法不為GET的話(huà) 直接返回父類(lèi)方法,系統(tǒng)本來(lái)怎么干的就讓它怎么干
if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) {
return [super cachedResponseForRequest:request];
}
// 核心方法
return [self dataFromRequest:request];
}//
- (void)removeAllCachedResponses {
[super removeAllCachedResponses];
[self deleteCacheFolder];
}//
- (void)removeCachedResponseForRequest:(NSURLRequest *)request {
[super removeCachedResponseForRequest:request];
NSString *url = request.URL.absoluteString;
NSString *fileName = [self cacheRequestFileName:url];
NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url];
NSString *filePath = [self cacheFilePath:fileName];
NSString *otherInfoPath = [self cacheFilePath:otherInfoFileName];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:filePath error:nil];
[fileManager removeItemAtPath:otherInfoPath error:nil];
}//
#pragma mark - custom url cache
- (NSString *)cacheFolder {
return @"URLCACHE";
}//
- (void)deleteCacheFolder {
NSString *path = [NSString stringWithFormat:@"%@/%@", self.diskPath, [self cacheFolder]];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtPath:path error:nil];
}//
- (NSString *)cacheFilePath:(NSString *)file {
NSString *path = [NSString stringWithFormat:@"%@/%@", self.diskPath, [self cacheFolder]];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir;
if ([fileManager fileExistsAtPath:path isDirectory:&isDir] && isDir) {
} else {
[fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
return [NSString stringWithFormat:@"%@/%@", path, file];
}//
- (NSString *)cacheRequestFileName:(NSString *)requestUrl {
//對(duì)傳進(jìn)來(lái)的url進(jìn)行md5 加密 ,加密后變成32位字符串,作為文件名保存
return [BaseTools md5Hash:requestUrl];
}//
- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl {
//同上
return [BaseTools md5Hash:[NSString stringWithFormat:@"%@-otherInfo", requestUrl]];
}//
- (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request {
//此為GET的情況
// 這方法會(huì)返回多次 每一次鏈接相同的url(有網(wǎng)絡(luò)的情況下,部分網(wǎng)頁(yè)如:(百度),它這個(gè)url里面可能內(nèi)嵌了很多其他的url,那其他的url可能每次都不一樣,所以返回的request.url.absluteString 都不一樣,這樣導(dǎo)致每次系統(tǒng)會(huì)根據(jù)absoluteStr 來(lái)創(chuàng)建文件,則會(huì)越來(lái)越多;但針對(duì)作業(yè)的App里面涉及到的網(wǎng)頁(yè)鏈接不會(huì)這樣。
NSString *url = request.URL.absoluteString;
//md5 加密
NSString *fileName = [self cacheRequestFileName:url];
NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url];
//filePath 用于保存網(wǎng)頁(yè)數(shù)據(jù)
NSString *filePath = [self cacheFilePath:fileName];
//otherInfoPath ?用于保存該url 對(duì)應(yīng)的一些配置屬性,如創(chuàng)建時(shí)間,MIMEType等。。
NSString *otherInfoPath = [self cacheFilePath:otherInfoFileName];
NSDate *date = [NSDate date];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
// expire 為過(guò)期的
BOOL expire = false;
NSDictionary *otherInfo = [NSDictionary dictionaryWithContentsOfFile:[otherInfoPath stringByAppendingString:@".plist"]];
//cacheTime ?為磁盤(pán)緩存文件在硬盤(pán)中保存的時(shí)間
//cacheTime 為0 時(shí)則永遠(yuǎn)不會(huì)過(guò)期
if (self.cacheTime > 0) {
NSInteger createTime = [[otherInfo objectForKey:@"time"] intValue];
if (createTime + self.cacheTime < [date timeIntervalSince1970]) {
expire = true;
}
}
if (expire == false ) {
NSLog(@"data from cache ...");
//發(fā)現(xiàn)緩存文件夾里面有緩存在硬盤(pán)的文件
NSData *data = [NSData dataWithContentsOfFile:filePath];
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:[otherInfo objectForKey:@"MIMEType"]
expectedContentLength:data.length
textEncodingName:[otherInfo objectForKey:@"textEncodingName"]];
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data] ;
return cachedResponse;
} else {
NSLog(@"cache expire ... ");
//過(guò)期了要?jiǎng)h除
[fileManager removeItemAtPath:filePath error:nil];
[fileManager removeItemAtPath:[NSString stringWithFormat:@"%@",[otherInfoPath stringByAppendingString:@".plist"]] error:nil];
}
}
if (![self isReachability]) {
return nil;
}
// 有網(wǎng)絡(luò)的狀態(tài)下進(jìn)行內(nèi)容的緩存
__block NSCachedURLResponse *cachedResponse = nil;
//sendAsynchronousRequest請(qǐng)求也要經(jīng)過(guò)NSURLCache
//如果沒(méi)有response 和data 的話(huà), 那字典對(duì)應(yīng)的value 為true,方法直接返回nil(此鏈接不能使用緩存);
id boolExsit = [self.responseDictionary objectForKey:url];
if (boolExsit == nil) {
[self.responseDictionary setValue:[NSNumber numberWithBool:TRUE] forKey:url];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data,NSError *error)
{
// 如果有data 和response 返回的話(huà)
if (response && data) {
//因?yàn)閏achesResponse 這個(gè)方法會(huì)被調(diào)用多次,所有dataFromrequest也會(huì)被調(diào)用多次,那如果服務(wù)器返回有response 和data的話(huà),就把responDicionary 這個(gè)字典清空,并把對(duì)應(yīng)的data寫(xiě)入,并把對(duì)應(yīng)的data和response 構(gòu)建成Cacheresponse 返回
[self.responseDictionary removeObjectForKey:url];
if (error) {
NSLog(@"error : %@", error);
NSLog(@"not cached: %@", request.URL.absoluteString);
cachedResponse = nil;
}
NSLog(@"---");
//save to cache
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%f", [date timeIntervalSince1970]], @"time",
response.MIMEType, @"MIMEType",
response.textEncodingName, @"textEncodingName", nil];
BOOL dictSuccess = [dict writeToFile:[otherInfoPath stringByAppendingString:@".plist"] atomically:YES];
BOOL dataSuccess = [data writeToFile:filePath atomically:YES];
if (!dictSuccess) {
NSLog(@"字典失敗");
}
if (!dataSuccess) {
NSLog(@"data 失敗");
}
cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data] ;
}
}];
return cachedResponse;
}
return nil;
} //
@end
鑒于挺多讀者可能看不到demo的下載地址,這里再列一下