? ? ?最近作者做的項目中需要用到UIWebView的離線緩存功能,本來滿心歡喜的想著在UIWebView的代理方法中看看有沒有什么代理方法可以直接做到緩存的功能鲁冯,結(jié)果還是太天真了拷沸,后來網(wǎng)上搜索了一下(主要參考了在code4app上面rusking作業(yè)對UIWebView離線瀏覽的代碼實現(xiàn)(地址https://github.com/lzhlewis2015/UIWebViewLocalCache),研究的過程中也花了不少時間薯演,所以想在這里把我的心得分享一下)撞芍,發(fā)現(xiàn)可以使用NSURLCache這個類實現(xiàn)。原理就是大多數(shù)的網(wǎng)絡(luò)請求都會先調(diào)用這個類中的- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request 這個方法跨扮,那我們只要重寫這個類序无,就能達到本地緩存的目的了。
下面是大致的邏輯
1 判斷請求中的request 是不是使用get方法衡创,據(jù)資料顯示一些本地請求的協(xié)議也會進到這個方法里面來帝嗡,所以在第一部,要把不相關(guān)的請求排除掉璃氢。
2 判斷緩存文件夾里面是否存在該文件哟玷,如果存在,繼續(xù)判斷文件是否過期一也,如果過期巢寡,則刪除。如果文件沒有過期椰苟,則提取文件抑月,然后組成NSCacheURLResponse返回到方法當(dāng)中。
3在有網(wǎng)絡(luò)的情況下舆蝴,如果文件夾中不存在該文件谦絮,則利用NSConnection這個類發(fā)網(wǎng)絡(luò)請求题诵,再把返回的data和response 數(shù)據(jù)本地化存儲起來,然后組成NSCacheURLResponse返回到方法當(dāng)中层皱。
4其中BaseTools和其他沒有在本.m文件中定義的類為常用的工具類性锭,這里不一一展開了。
大致邏輯就這么多奶甘,話不多說篷店,直接看代碼實現(xiàn)(關(guān)鍵代碼有注釋):
#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è)為60臭家,則60秒之后本地緩存文件過期)
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 {
// 這里判斷如果請求方法不為GET的話 直接返回父類方法,系統(tǒng)本來怎么干的就讓它怎么干
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 {
//對傳進來的url進行md5 加密 ,加密后變成32位字符串方淤,作為文件名保存
return [BaseTools md5Hash:requestUrl];
}//
- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl {
//同上
return [BaseTools md5Hash:[NSString stringWithFormat:@"%@-otherInfo", requestUrl]];
}//
- (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request {
//此為GET的情況
// 這方法會返回多次 每一次鏈接相同的url(有網(wǎng)絡(luò)的情況下钉赁,部分網(wǎng)頁如:(百度),它這個url里面可能內(nèi)嵌了很多其他的url携茂,那其他的url可能每次都不一樣你踩,所以返回的request.url.absluteString 都不一樣,這樣導(dǎo)致每次系統(tǒng)會根據(jù)absoluteStr 來創(chuàng)建文件讳苦,則會越來越多;但針對作業(yè)的App里面涉及到的網(wǎng)頁鏈接不會這樣带膜。
NSString *url = request.URL.absoluteString;
//md5 加密
NSString *fileName = [self cacheRequestFileName:url];
NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url];
//filePath 用于保存網(wǎng)頁數(shù)據(jù)
NSString *filePath = [self cacheFilePath:fileName];
//otherInfoPath ?用于保存該url 對應(yīng)的一些配置屬性,如創(chuàng)建時間鸳谜,MIMEType等膝藕。。
NSString *otherInfoPath = [self cacheFilePath:otherInfoFileName];
NSDate *date = [NSDate date];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
// expire 為過期的
BOOL expire = false;
NSDictionary *otherInfo = [NSDictionary dictionaryWithContentsOfFile:[otherInfoPath stringByAppendingString:@".plist"]];
//cacheTime ?為磁盤緩存文件在硬盤中保存的時間
//cacheTime 為0 時則永遠不會過期
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)緩存文件夾里面有緩存在硬盤的文件
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 ... ");
//過期了要刪除
[fileManager removeItemAtPath:filePath error:nil];
[fileManager removeItemAtPath:[NSString stringWithFormat:@"%@",[otherInfoPath stringByAppendingString:@".plist"]] error:nil];
}
}
if (![self isReachability]) {
return nil;
}
// 有網(wǎng)絡(luò)的狀態(tài)下進行內(nèi)容的緩存
__block NSCachedURLResponse *cachedResponse = nil;
//sendAsynchronousRequest請求也要經(jīng)過NSURLCache
//如果沒有response 和data 的話咐扭, 那字典對應(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 返回的話
if (response && data) {
//因為cachesResponse 這個方法會被調(diào)用多次,所有dataFromrequest也會被調(diào)用多次,那如果服務(wù)器返回有response 和data的話蝗肪,就把responDicionary 這個字典清空袜爪,并把對應(yīng)的data寫入,并把對應(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的下載地址薛闪,這里再列一下