本文主要是介绍iOS之网页缓存html----NSURLCache-----NSURLProtocol,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
//写入缓存
- (void)writeToCache
{
NSString * htmlResponseStr = [NSString stringWithContentsOfURL:self.url encoding:NSUTF8StringEncoding error:Nil];
//创建文件管理器
NSFileManager *fileManager = [NSFileManagerdefaultManager];
//获取document路径
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)objectAtIndex:0];
[fileManager createDirectoryAtPath:[cachesPathstringByAppendingString:@"/Caches"]withIntermediateDirectories:YESattributes:nilerror:nil];
//写入路径
NSString * path = [cachesPath stringByAppendingString:[NSString stringWithFormat:@"/Caches/%lu.html",(unsignedlong)[[self.url absoluteString] hash]]];
[htmlResponseStr writeToFile:pathatomically:YESencoding:NSUTF8StringEncodingerror:nil];
}
//有缓存就加载缓存,没缓存就从服务器加载
- (void)viewDidLoad
{
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)objectAtIndex:0];
NSString * path = [cachesPath stringByAppendingString:[NSString stringWithFormat:@"/Caches/%lu.html",(unsignedlong)[[self.url absoluteString] hash]]];
NSString *htmlString = [NSStringstringWithContentsOfFile:pathencoding:NSUTF8StringEncodingerror:nil];
if (!(htmlString ==nil || [htmlStringisEqualToString:@""])) {
[self.webView loadHTMLString:htmlString baseURL:self.url];
}else{
NSURLRequest *request = [NSURLRequest requestWithURL:self.url];
[_webView loadRequest:request];
[self writeToCache];
}
}
原理就是SDK里绝大部分的网络请求都会访问[NSURLCache sharedURLCache]这个对象,它的cachedResponseForRequest:方法会返回一个NSCachedURLResponse对象。如果这个NSCachedURLResponse对象不为nil,且没有过期,那么就使用这个缓存的响应,否则就发起一个不访问缓存的请求。
要注意的是NSCachedURLResponse对象不能被提前释放,除非UIWebView去调用NSURLCache的removeCachedResponseForRequest:方法,原因貌似是UIWebView并不retain这个响应。而这个问题又很头疼,因为UIWebView有内存泄露的嫌疑,即使它被释放了,也很可能不去调用上述方法,于是内存就一直占用着了。
顺便说下NSURLRequest对象,它有个cachePolicy属性,只要其值为NSURLRequestReloadIgnoringLocalCacheData的话,就不会访问缓存。可喜的是这种情况貌似只有在缓存里没取到,或是强制刷新时才可能出现。
实际上NSURLCache本身就有磁盘缓存功能,然而在iOS上,NSCachedURLResponse却被限制为不能缓存到磁盘(NSURLCacheStorageAllowed被视为NSURLCacheStorageAllowedInMemoryOnly)。
不过既然知道了原理,那么只要自己实现一个NSURLCache的子类,然后改写cachedResponseForRequest:方法,让它从硬盘读取缓存即可。
#import <UIKit/UIKit.h>
@interface ViewController :UIViewController <UIWebViewDelegate>
@property(nonatomic,retain)UIWebView *webView;
@end
#import "ViewController.h"
#import "CustomURLCache.h"
#import "MBProgressHUD.h"
@interface ViewController ()
@end
@implementation ViewController
@synthesize webView = _webView;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [superinitWithNibName:nibNameOrNilbundle:nibBundleOrNil]) {
CustomURLCache *urlCache = [[CustomURLCachealloc]initWithMemoryCapacity:20 * 1024 * 1024
diskCapacity:200 * 1024 * 1024
diskPath:nil
cacheTime:0];
[CustomURLCachesetSharedURLCache:urlCache];
[urlCache release];
}
returnself;
}
- (void)viewDidLoad {
[superviewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIWebView *webView = [[UIWebViewalloc]initWithFrame:self.view.frame];
webView.delegate =self;
self.webView = webView;
[webView release];
[self.viewaddSubview:_webView];
[self.webViewloadRequest:[NSURLRequestrequestWithURL:[NSURLURLWithString:@"http://www.baidu.com/"]]];
}
- (void)didReceiveMemoryWarning
{
[superdidReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
CustomURLCache *urlCache = (CustomURLCache *)[NSURLCachesharedURLCache];
[urlCache removeAllCachedResponses];
}
- (void)dealloc {
[_webViewrelease];
[superdealloc];
}
#pragma mark - webview
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[MBProgressHUDhideHUDForView:self.viewanimated:YES];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
[MBProgressHUDhideHUDForView:self.viewanimated:YES];
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
MBProgressHUD *hud = [MBProgressHUDshowHUDAddedTo:self.viewanimated:YES];
hud.mode =MBProgressHUDModeIndeterminate;
hud.labelText =@"Loading...";
}
@end
#import <Foundation/Foundation.h>
#import "Util.h"
@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
=========#import "CustomURLCache.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;
- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime {
if (self = [selfinitWithMemoryCapacity:memoryCapacitydiskCapacity:diskCapacitydiskPath:path]) {
self.cacheTime = cacheTime;
if (path)
self.diskPath = path;
else
self.diskPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES)lastObject];
self.responseDictionary = [NSMutableDictionarydictionaryWithCapacity:0];
}
returnself;
}
- (void)dealloc {
[_diskPathrelease];
[_responseDictionaryrelease];
[superdealloc];
}
原理就是SDK里绝大部分的网络请求都会访问[NSURLCache sharedURLCache]这个对象,它的cachedResponseForRequest:方法会返回一个NSCachedURLResponse对象。如果这个NSCachedURLResponse对象不为nil,且没有过期,那么就使用这个缓存的响应,否则就发起一个不访问缓存的请求。
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
if ([request.HTTPMethodcompare:@"GET"] !=NSOrderedSame) {
return [supercachedResponseForRequest:request];
}
return [selfdataFromRequest:request];
}
- (void)removeAllCachedResponses {
[superremoveAllCachedResponses];
[selfdeleteCacheFolder];
}
- (void)removeCachedResponseForRequest:(NSURLRequest *)request {
[superremoveCachedResponseForRequest:request];
NSString *url = request.URL.absoluteString;
NSString *fileName = [selfcacheRequestFileName:url];
NSString *otherInfoFileName = [selfcacheRequestOtherInfoFileName:url];
NSString *filePath = [selfcacheFilePath:fileName];
NSString *otherInfoPath = [selfcacheFilePath:otherInfoFileName];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
[fileManager removeItemAtPath:filePatherror:nil];
[fileManager removeItemAtPath:otherInfoPatherror:nil];
}
#pragma mark - custom url cache
- (NSString *)cacheFolder {
return@"URLCACHE";
}
- (void)deleteCacheFolder {
NSString *path = [NSStringstringWithFormat:@"%@/%@",self.diskPath, [selfcacheFolder]];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
[fileManager removeItemAtPath:patherror:nil];
}
- (NSString *)cacheFilePath:(NSString *)file {
NSString *path = [NSStringstringWithFormat:@"%@/%@",self.diskPath, [selfcacheFolder]];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
BOOL isDir;
if ([fileManagerfileExistsAtPath:pathisDirectory:&isDir] && isDir) {
} else {
[fileManager createDirectoryAtPath:pathwithIntermediateDirectories:YESattributes:nilerror:nil];
}
return [NSStringstringWithFormat:@"%@/%@", path, file];
}
- (NSString *)cacheRequestFileName:(NSString *)requestUrl {
return [Utilmd5Hash:requestUrl];
}
- (NSString *)cacheRequestOtherInfoFileName:(NSString *)requestUrl {
return [Utilmd5Hash:[NSStringstringWithFormat:@"%@-otherInfo", requestUrl]];
}
- (NSCachedURLResponse *)dataFromRequest:(NSURLRequest *)request {
NSString *url = request.URL.absoluteString;
NSString *fileName = [selfcacheRequestFileName:url];
NSString *otherInfoFileName = [selfcacheRequestOtherInfoFileName:url];
NSString *filePath = [selfcacheFilePath:fileName];
NSString *otherInfoPath = [selfcacheFilePath:otherInfoFileName];
NSDate *date = [NSDatedate];
NSFileManager *fileManager = [NSFileManagerdefaultManager];
if ([fileManagerfileExistsAtPath:filePath]) {
BOOL expire =false;
NSDictionary *otherInfo = [NSDictionarydictionaryWithContentsOfFile:otherInfoPath];
if (self.cacheTime > 0) {
NSInteger createTime = [[otherInfoobjectForKey:@"time"]intValue];
if (createTime +self.cacheTime < [datetimeIntervalSince1970]) {
expire = true;
}
}
if (expire ==false) {
NSLog(@"data from cache ...");
NSData *data = [NSDatadataWithContentsOfFile:filePath];
NSURLResponse *response = [[NSURLResponsealloc]initWithURL:request.URL
MIMEType:[otherInfo objectForKey:@"MIMEType"]
expectedContentLength:data.length
textEncodingName:[otherInfoobjectForKey:@"textEncodingName"]];
NSCachedURLResponse *cachedResponse = [[[NSCachedURLResponsealloc]initWithResponse:responsedata:data]autorelease];
[response release];
return cachedResponse;
} else {
NSLog(@"cache expire ... ");
[fileManager removeItemAtPath:filePatherror:nil];
[fileManager removeItemAtPath:otherInfoPatherror:nil];
}
}
__blockNSCachedURLResponse *cachedResponse =nil;
//sendSynchronousRequest请求也要经过NSURLCache
id boolExsite = [self.responseDictionaryobjectForKey:url];
if (boolExsite ==nil) {
[self.responseDictionarysetValue:[NSNumbernumberWithBool:TRUE]forKey:url];
[NSURLConnectionsendAsynchronousRequest:requestqueue:[[NSOperationQueuealloc]init]completionHandler:^(NSURLResponse *response,NSData *data,NSError *error)
{
[self.responseDictionaryremoveObjectForKey:url];
if (error) {
NSLog(@"error : %@", error);
NSLog(@"not cached: %@", request.URL.absoluteString);
cachedResponse = nil;
}
NSLog(@"get request ... ");
//save to cache
NSDictionary *dict = [NSDictionarydictionaryWithObjectsAndKeys:[NSStringstringWithFormat:@"%f", [datetimeIntervalSince1970]],@"time",
response.MIMEType,@"MIMEType",
response.textEncodingName,@"textEncodingName",nil];
[dict writeToFile:otherInfoPathatomically:YES];
[data writeToFile:filePathatomically:YES];
cachedResponse = [[[NSCachedURLResponsealloc]initWithResponse:responsedata:data]autorelease];
}];
return cachedResponse;
//NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
returnnil;
}
@end
#import "Util.h"
#import <CommonCrypto/CommonDigest.h>
@implementation Util
+ (NSString *)sha1:(NSString *)str {
constchar *cstr = [strcStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSDatadataWithBytes:cstrlength:str.length];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, data.length, digest);
NSMutableString* output = [NSMutableStringstringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i = 0; i <CC_SHA1_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
+ (NSString *)md5Hash:(NSString *)str {
constchar *cStr = [strUTF8String];
unsignedchar result[16];
CC_MD5( cStr,strlen(cStr), result );
NSString *md5Result = [NSStringstringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
return md5Result;
}
@end
NSURLProtocol是个抽象类,只要理解为不能直接实例化它,想用它的方法,就去继承它.
NSURLProtocol是NSURLConnection的handle类, 它更像一套协议,如果遵守这套协议,网络请求Request都会经过这套协议里面的方法去处理.
再说简单点,就是对上层的URLRequest请求做拦截,并根据自己的需求场景做定制化响应处理。

图解 : NSURLProtocol 能在系统执行 URLRequest前先去将URLRequest处理了一遍.
// 这个方法是注册NSURLProtocol子类的方法.
+ (BOOL)registerClass:(Class)protocolClass;
// 这个方法是注册后,NSURLProtocol就会通过这个方法确定参数request是否需要被处理
// return : YES 需要经过这个NSURLProtocol"协议"的处理, NO这个协议request不需要遵守这个NSURLProtocol"协议"
// 这个方法的左右 : 1,筛选Request是否需要遵守这个NSURLRequest , 2,处理http: , https等URL
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
// 这个方法就是返回request,当然这里可以处理的需求有 : 1,规范化请求头的信息 2, 处理DNS劫持,重定向App中所有的请求指向等
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
// 这个方法主要用来判断两个请求是否是同一个请求,如果是,则可以使用缓存数据,通常只需要调用父类的实现即可,默认为YES,而且一般不在这里做事情
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b;
// abstract Initializes an NSURLProtocol given request, cached response, and client.
// 开始初始化一个NSURLProtocol抽象对象,包含请求, cachedResponse ,和建立client
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(nullable NSCachedURLResponse *)cachedResponse client:(nullableid <NSURLProtocolClient>)clientNS_DESIGNATED_INITIALIZER;
// 需要在该方法中发起一个请求,对于NSURLConnection来说,就是创建一个NSURLConnection,对于NSURLSession,就是发起一个NSURLSessionTask
// 另外一点就是这个方法之后,会回调<NSURLProtocolClient>协议中的方法,
<NSURLProtocolClient> 的协议方法, 一般和NSURLConnection的代理方法一起使用
- (void)startLoading
// 这个方法是和start是对应的一般在这个方法中,断开Connection
// 另外一点就是当NSURLProtocolClient的协议方法都回调完毕后,就会开始执行这个方法了
- (void)stopLoading
static NSString *const TravinProtocolHandledKey =@"TravinProtocolHandledKey";
这个方法是注册后,NSURLProtocol就会通过这个方法确定参数request是否需要被处理
// return : YES 需要经过这个NSURLProtocol"协议"的处理, NO这个协议request不需要遵守这个NSURLProtocol"协议"
// 这个方法的左右 : 1,筛选Request是否需要遵守这个NSURLRequest , 2,处理http: , https等URL
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//只处理http和https请求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:TravinProtocolHandledKey inRequest:request]) {
returnNO;
}
returnYES;
}
returnNO;
}
// 这个方法就是返回request,当然这里可以处理的需求有 :
// 1,规范化请求头的信息 2,处理DNS劫持,重定向App中所有的请求指向等
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
// 这个方法主要用来判断两个请求是否是同一个请求,
// 如果是,则可以使用缓存数据,通常只需要调用父类的实现即可,默认为YES,而且一般不在这里做事情
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
// 开始初始化一个NSURLProtocol抽象对象,包含请求, cachedResponse ,和建立client
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
self = [super initWithRequest:request cachedResponse:cachedResponse client:client];
if (self) {
}
returnself;
}
// 需要在该方法中发起一个请求,对于NSURLConnection来说,就是创建一个NSURLConnection,对于NSURLSession,就是发起一个NSURLSessionTask
// 另外一点就是这个方法之后,会回调<NSURLProtocolClient>协议中的方法,
- (void)startLoading
{
NSString *cacheKey = self.request.URL.absoluteString;
// 1.根据URL作为KEY,利用PRCachedURLResponse创建缓存
TravinCachedURLResponse *cachedResponse = [[TravinObjectCache sharedCache] objectForKey:cacheKey];//根据请求的URL,获取缓存,
if (cachedResponse && cachedResponse.response && cachedResponse.data) {// 如果有缓存
NSURLResponse *response = cachedResponse.response;
NSData *data = cachedResponse.data;
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
return;
}
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[newRequest setTimeoutInterval:15]; //设置超时请求
// 给我们处理过的请求设置一个标识符,防止无限循环,
[NSURLProtocol setProperty:@YES forKey:TravinProtocolHandledKey inRequest:newRequest];
// 1.根据URL作为KEY,利用PRCachedURLResponse创建缓存,如果没,则创建一个NSURLConnection,将处理的request与这个connection钩起来,同时实现NSConnectionDataDelegate的回调
self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self];// 创建connection
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//标示改request已经处理过了,防止无限循环
[NSURLProtocol setProperty:@YES forKey:TravinProtocolHandledKey inRequest:mutableReqeust];
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
// 这个方法是和start是对应的一般在这个方法中,断开Connection
// 另外一点就是当NSURLProtocolClient的协议方法都回调完毕后,就会开始执行这个方法了
- (void)stopLoading
{
// 断开连接
[self.connection cancel];
}
如果注册了两个NSURLProtocol,执行顺序是怎样?
Protocols的遍历是反向的,也就是最后注册的Protocol会被优先判断
它是一个抽象类,为载入URL的data的一些特定协议提供基础的结构。要实现它里面的函数就必须继承它,因此小Potti将在后面创建一个MWURLProtocol类继承它,并实现它其中的一系列函数。
而NSURLProtocol其中有个成员就是NSURLProtocolClient的一个实例。因为NSURLProtocol是由一系列的回调函数构成的(注册函数除外),而要对URL的data进行各种操作时就到了调用NSURLProtocolClient实例的时候了,这就实现了一个钩子,去操作URL data。
NSURLProtocol有以下一系列的回调方法
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client;
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
- (void)startLoading;
- (void)stopLoading;
其中canInitWithRequest是询问是否处理该请求的回调,如果不处理则后面所有函数都不会再调用。startLoading和stopLoading是分别对于loading开始从网页上抓取数据,从网页上抓取完数据的回调。其中startLoading称为我们可以重点利用的函数
NSURLProtocolClient主要有以下方法:
- (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
- (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
其中wasRedirectedToRequest是重定向函数,cachedResponseIsValid是对cached的操作,didReceiveResponse是受到Response时的调用处理函数, didLoadData是load完数据时的调用,
这篇关于iOS之网页缓存html----NSURLCache-----NSURLProtocol的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!