? ? ?最近作者做的項目中需要用到UIWebView的離線緩存功能,本來滿心歡喜的想著在UIWebView的代理方法中看看有沒有什么代理方法可以直接做到緩存的功能,結果還是太天真了,后來網上搜索了一下(主要參考了在code4app上面rusking作業對UIWebView離線瀏覽的代碼實現(地址https://github.com/lzhlewis2015/UIWebViewLocalCache),研究的過程中也花了不少時間,所以想在這里把我的心得分享一下),發現可以使用NSURLCache這個類實現。原理就是大多數的網絡請求都會先調用這個類中的- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request 這個方法,那我們只要重寫這個類,就能達到本地緩存的目的了。
下面是大致的邏輯
1 判斷請求中的request 是不是使用get方法,據資料顯示一些本地請求的協議也會進到這個方法里面來,所以在第一部,要把不相關的請求排除掉。
2 判斷緩存文件夾里面是否存在該文件,如果存在,繼續判斷文件是否過期,如果過期,則刪除。如果文件沒有過期,則提取文件,然后組成NSCacheURLResponse返回到方法當中。
3在有網絡的情況下,如果文件夾中不存在該文件,則利用NSConnection這個類發網絡請求,再把返回的data和response 數據本地化存儲起來,然后組成NSCacheURLResponse返回到方法當中。
4其中BaseTools和其他沒有在本.m文件中定義的類為常用的工具類,這里不一一展開了。
大致邏輯就這么多,話不多說,直接看代碼實現(關鍵代碼有注釋):
#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 為你所希望本地緩存的時間(以秒計算,如果設為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的話 直接返回父類方法,系統本來怎么干的就讓它怎么干
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(有網絡的情況下,部分網頁如:(百度),它這個url里面可能內嵌了很多其他的url,那其他的url可能每次都不一樣,所以返回的request.url.absluteString 都不一樣,這樣導致每次系統會根據absoluteStr 來創建文件,則會越來越多;但針對作業的App里面涉及到的網頁鏈接不會這樣。
NSString *url = request.URL.absoluteString;
//md5 加密
NSString *fileName = [self cacheRequestFileName:url];
NSString *otherInfoFileName = [self cacheRequestOtherInfoFileName:url];
//filePath 用于保存網頁數據
NSString *filePath = [self cacheFilePath:fileName];
//otherInfoPath ?用于保存該url 對應的一些配置屬性,如創建時間,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 ...");
//發現緩存文件夾里面有緩存在硬盤的文件
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;
}
// 有網絡的狀態下進行內容的緩存
__block NSCachedURLResponse *cachedResponse = nil;
//sendAsynchronousRequest請求也要經過NSURLCache
//如果沒有response 和data 的話, 那字典對應的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 這個方法會被調用多次,所有dataFromrequest也會被調用多次,那如果服務器返回有response 和data的話,就把responDicionary 這個字典清空,并把對應的data寫入,并把對應的data和response 構建成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的下載地址,這里再列一下