参考
http://re-reference.iteye.com/blog/1391408
http://nshipster.cn/nsurlcache/
http://blog.csdn.net/mad2man/article/details/30285001
SDK里绝大部分的网络请求都会访问[NSURLCache sharedURLCache]这个对象,它的cachedResponseForRequest:方法会返回一个NSCachedURLResponse对象。如果这个NSCachedURLResponse对象不为nil,且没有过期,那么就使用这个缓存的响应,否则就发起一个不访问缓存的请求。
要注意的是NSCachedURLResponse对象不能被提前释放,除非UIWebView去调用NSURLCache的removeCachedResponseForRequest:方法,原因貌似是UIWebView并不retain这个响应。而这个问题又很头疼,因为UIWebView有内存泄露的嫌疑,即使它被释放了,也很可能不去调用上述方法,于是内存就一直占用着了。
顺便说下NSURLRequest对象,它有个cachePolicy属性,只要其值为NSURLRequestReloadIgnoringLocalCacheData的话,就不会访问缓存。可喜的是这种情况貌似只有在缓存里没取到,或是强制刷新时才可能出现。
实际上NSURLCache本身就有磁盘缓存功能,然而在iOS上,NSCachedURLResponse却被限制为不能缓存到磁盘(NSURLCacheStorageAllowed被视为NSURLCacheStorageAllowedInMemoryOnly)。
不过既然知道了原理,那么只要自己实现一个NSURLCache的子类,然后改写cachedResponseForRequest:方法,让它从硬盘读取缓存即可。
第一种重写NSURLCache修改cachedResponseForRequest
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) {
return [super cachedResponseForRequest:request];
}
NSURL *url = request.URL;
if (![supportSchemes containsObject:url.scheme]) {
return [super cachedResponseForRequest:request];
}
return [self dataFromRequest:request];
}
- (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)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];
NSDate *date = [NSDate date];
// 判断磁盘里面是否有本文件如果有就加载缓存
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:filePath]) {
BOOL expire = false;
NSDictionary *otherInfo = [NSDictionary dictionaryWithContentsOfFile:otherInfoPath];
// 判断是否过期
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] autorelease];
[response release];
return cachedResponse;
} else {
NSLog(@"cache expire ... ");
[fileManager removeItemAtPath:filePath error:nil];
[fileManager removeItemAtPath:otherInfoPath error:nil];
}
}
if (![Reachability networkAvailable]) {
return nil;
}
// 如果缓存已经过期 而且有网络的话
__block NSCachedURLResponse *cachedResponse = nil;
//sendSynchronousRequest请求也要经过NSURLCache
id boolExsite = [self.responseDictionary objectForKey:url];
if (boolExsite == nil) {
[self.responseDictionary setValue:[NSNumber numberWithBool:TRUE] forKey:url];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data,NSError *error)
{
if (response && data) {
[self.responseDictionary removeObjectForKey:url];
if (error) {
NSLog(@"error : %@", error);
NSLog(@"not cached: %@", request.URL.absoluteString);
cachedResponse = nil;
}
NSLog(@"cache url --- %@ ",url);
//save to cache
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"%f", [date timeIntervalSince1970]], @"time",
response.MIMEType, @"MIMEType",
response.textEncodingName, @"textEncodingName", nil];
[dict writeToFile:otherInfoPath atomically:YES];
[data writeToFile:filePath atomically:YES];
cachedResponse = [[[NSCachedURLResponse alloc] initWithResponse:response data:data] autorelease];
}
}];
return cachedResponse;
//NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
return nil;
}
通过MD5hash生成文件名
MD5hash,哈希值计算器,是一款md5校验工具。每个文件都可以用Hash MD5验证程序算出一个固定的MD5码来。
- (NSString *)cacheRequestFileName:(NSString *)requestUrl {
return [Util md5Hash:requestUrl];
}
- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl {
return [Util md5Hash:[NSString stringWithFormat:@"%@-otherInfo", requestUrl]];
}
第二种利用ASIHTTPRequest来缓存
ASIHTTPRequest,ASIDownloadCache 和 ASIWebPageRequest
首先我得说,这确实是个很好的框架,使用起来确实很方便,但是对于缓存这个问题,好像也跟第二点提到的效果差不多,加载速度没有明显的提升,离线模式下也无法加载。这是实现的代码:
- -(void)loadURL:(NSURL*)url
- {
- ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
- //ASIWebPageRequest *request= [ASIWebPageRequest requestWithURL:url];
- [request setDelegate:self];
- //[request setUrlReplacementMode:ASIReplaceExternalResourcesWithData];
- [request setDidFailSelector:@selector(webPageFetchFailed:)];
- [request setDidFinishSelector:@selector(webPageFetchSucceeded:)];
- //设置缓存
- [request setDownloadCache:[ASIDownloadCache sharedCache]];
- //[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
- [request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];
- [request setDownloadDestinationPath:[[ASIDownloadCache sharedCache]pathToStoreCachedResponseDataForRequest:request]];
- [request startAsynchronous];
- }
-
-
-
- - (void)webPageFetchFailed:(ASIHTTPRequest *)theRequest
- {
- // Obviously you should handle the error properly...
- NSLog(@"%@",[theRequest error]);
- NSString *path = [[NSBundle mainBundle] pathForResource:@"error1.html" ofType:nil inDirectory:@"WebResources/Error"];
- NSURL *url=[NSURL fileURLWithPath:path];
- [viewer loadRequest:[NSURLRequest requestWithURL:url]];
- }
-
- - (void)webPageFetchSucceeded:(ASIHTTPRequest *)theRequest
- {
- NSString *response = [NSString stringWithContentsOfFile:
- [theRequest downloadDestinationPath] encoding:[theRequest responseEncoding] error:nil];
- // Note we're setting the baseURL to the url of the page we downloaded. This is important!
- [viewer loadHTMLString:response baseURL:[theRequest url]];
- //[viewer loadHTMLString:response baseURL:nil];
- }
- -(void)loadURL:(NSURL*)url
- {
- ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
- //ASIWebPageRequest *request= [ASIWebPageRequest requestWithURL:url];
- [request setDelegate:self];
- //[request setUrlReplacementMode:ASIReplaceExternalResourcesWithData];
- [request setDidFailSelector:@selector(webPageFetchFailed:)];
- [request setDidFinishSelector:@selector(webPageFetchSucceeded:)];
- //设置缓存
- [request setDownloadCache:[ASIDownloadCache sharedCache]];
- //[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];
- [request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];
- [request setDownloadDestinationPath:[[ASIDownloadCache sharedCache]pathToStoreCachedResponseDataForRequest:request]];
- [request startAsynchronous];
- }
- - (void)webPageFetchFailed:(ASIHTTPRequest *)theRequest
- {
- // Obviously you should handle the error properly...
- NSLog(@"%@",[theRequest error]);
- NSString *path = [[NSBundle mainBundle] pathForResource:@"error1.html" ofType:nil inDirectory:@"WebResources/Error"];
- NSURL *url=[NSURL fileURLWithPath:path];
- [viewer loadRequest:[NSURLRequest requestWithURL:url]];
- }
- - (void)webPageFetchSucceeded:(ASIHTTPRequest *)theRequest
- {
- NSString *response = [NSString stringWithContentsOfFile:
- [theRequest downloadDestinationPath] encoding:[theRequest responseEncoding] error:nil];
- // Note we're setting the baseURL to the url of the page we downloaded. This is important!
- [viewer loadHTMLString:response baseURL:[theRequest url]];
- //[viewer loadHTMLString:response baseURL:nil];
- }