大家有没有遇到过项目有要求对UIWebView的请求数据进行缓存,在有缓存的情况下,从本地加载数据,如果没有缓存,或者缓存已经过期的情况下,则从服务端进行加载数据,并对其返还结果进行缓存操作,如果遇到这种需求,大家会有怎样的解决方案呢?以下是参考http://www.jianshu.com/p/7f3be7c30c77的解决方案。
第一种方案:
原理就是大多数的网络请求都会先调用这个类中的- (NSCachedURLResponse )cachedResponseForRequest:(NSURLRequest )request 这个方法,那我们只要重写这个类,就能达到本地缓存的目的了。
下面是大致的逻辑
1.判断请求中的request 是不是使用get方法,据资料显示一些本地请求的协议也会进到这个方法里面来,所以在第一部,要把不相关的请求排除掉。
2.判断缓存文件夹里面是否存在该文件,如果存在,继续判断文件是否过期,如果过期,则删除。如果文件没有过期,则提取文件,然后组成NSCacheURLResponse返回到方法当中。
3.在有网络的情况下,如果文件夹中不存在该文件,则利用NSConnection这个类发网络请求,再把返回的data和response 数据本地化存储起来,然后组成NSCacheURLResponse返回到方法当中。
具体代码如下:
1.定义一个CustomURLCache 继承于NSURLCache
@interface CustomURLCache : NSURLCache
@property(nonatomic, assign) NSInteger cacheTime;
@property(nonatomic, retain) NSString *diskPath;
@property(nonatomic, retain) NSMutableDictionary *responseDictionary;
- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime;
@end
.m文件如下:
#import "CustomURLCache.h"
#import "Reachability.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
@synthesize cacheTime = _cacheTime;
@synthesize diskPath = _diskPath;
@synthesize responseDictionary = _responseDictionary;
/**初始化方法
@param memoryCapacity 缓存内存大小,默认4M,4* 1024 * 1024
@param diskCapacity 硬盘大小,默认20M,20 * 1024 * 1024
@param path 缓存路径,默认(NSHomeDirectory)/Library/Caches/(current application name, [[NSProcessInfo processInfo] processName])
@param cacheTime 缓存有效时间
@return 自定义NSURLCache对象
*/
- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime {
if (self = [self initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) {
self.cacheTime = cacheTime;
if (path)
self.diskPath = path;
else
self.diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
self.responseDictionary = [NSMutableDictionary dictionaryWithCapacity:0];
}
return self;
}
- (void)dealloc {
[_diskPath release];
[_responseDictionary release];
[super dealloc];
}
//所有网络请求都会优先调用该方法,所有我们只要在这里重载这个方法,对请求进行处理即可
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
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 {
return [Util md5Hash:requestUrl];
}
- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl {
return [Util md5Hash:[NSString stringWithFormat:@"%@-otherInfo", requestUrl]];
}
/*关键方法,如果没有缓存,则返回nil,有则返回一个NSCachedURLResponse对象*/
- (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) {
//cacheTime == 0,则表示永久有效
NSInteger createTime = [[otherInfo objectForKey:@"time"] intValue];
if (createTime + self.cacheTime < [date timeIntervalSince1970]) {
expire = true;
}
}
if (expire == false) {
NSLog(@"data from cache ...");
//从缓存读取数据,初始化一个NSCachedURLResponse对象
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;
}
//sendSynchronousRequest请求也要经过NSURLCache,从新进行数据缓存
__block NSCachedURLResponse * cachedResponse = nil;
id boolExsite = [self.responseDictionary objectForKey:url];
if (boolExsite == nil) {
[self.responseDictionary setValue:[NSNumber numberWithBool:TRUE] forKey:url];
// NSURLResponse *response = nil;
// NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
if (response && data) {
[self.responseDictionary removeObjectForKey:url];
}
if (connectionError) {
NSLog(@"error : %@", connectionError);
NSLog(@"not cached: %@", request.URL.absoluteString);
cachedResponse = nil;
}
NSLog(@"get request ... ");
//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;
}
return nil;
}
@end
具体使用如下:
//关键:类初始化时进行,改变默认的NSURLCache
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
CustomURLCache *urlCache = [[CustomURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:200 * 1024 * 1024
diskPath:nil
cacheTime:0];
//setSharedURLCache该方法可以使缓存类变为我们自定义的缓存方案
[CustomURLCache setSharedURLCache:urlCache];
[urlCache release];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.frame];
webView.delegate = self;
self.webView = webView;
[webView release];
[self.view addSubview:_webView];
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com/"]]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
CustomURLCache *urlCache = (CustomURLCache *)[NSURLCache sharedURLCache];
[urlCache removeAllCachedResponses];
}
- (void)dealloc {
[_webView release];
[super dealloc];
}
#pragma mark - webview
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.mode = MBProgressHUDModeIndeterminate;
hud.labelText = @"Loading...";
}
注意: 这方案会返回多次 每一次链接相同的url(有网络的情况下,部分网页如:(百度),它这个url里面可能内嵌了很多其他的url,那其他的url可能每次都不一样,所以返回的request.url.absluteString 都不一样,这样导致每次系统会根据absoluteStr 来创建文件,则会越来越多;这种情况下是不适用的。
源码地址:https://github.com/lzhlewis2015/UIWebViewLocalCache
第二种方案:
+ (instancetype)requestWithURL:(NSURL *)URL cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval;
//通过上面方法进行设置NSURLRequestCachePolicy
- (void)loadWebView
{
//可以每次请求的时候先去缓存里面读取,如果缓存里面没有的话再去网上加载
request = [NSURLRequest requestWithURL:urlcachePolicy:NSURLRequestReturnCacheDataElseLad timeoutInterval:10.];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
if (cachedResponse) {
//cache已经存在 直接用web加载本地的数据
NSLog(@"cache存在 直接加载本地的数据");
[_WKWebView loadHTMLString:[[NSString alloc]initWithData:cachedResponse.data encoding:NSUTF8StringEncoding] baseURL:_request.URL];}
}else {
//cache还不存在
NSLog(@"cache不存在 需要从网络加载");
NSURLSessionDownloadTask *task = [[NSURLSession sharedSession]downloadTaskWithRequest:_request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSData *data = [NSData dataWithContentsOfURL:location];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:_request];
if ((!cachedResponse) && response && data) {
NSCachedURLResponse *cachedURLResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];
[[NSURLCache sharedURLCache]storeCachedResponse:cachedURLResponse forRequest:_request];}
}];
[task resume];
[_WKWebView loadRequest:request];}
}
//这个清除缓存也很简单
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:request];