前几天项目中,为了增加用户体验,让用户在没有网络的情况下仍然可以加载之前缓存的UIWebView,用到了离线缓存方面的技术,特此分享出来。为了考虑大家的项目语言不同,我将提供OC版本和Swift3.0版本,希望能帮助大家。
NSURLProtocol介绍
NSURLProtocol能够让你去重新定义苹果的URL加载系统 (URL Loading System)的行为,URL Loading System里有许多类用于处理URL请求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等,当URL Loading System使用NSURLRequest去获取资源的时候,它会创建一个NSURLProtocol子类的实例,你不应该直接实例化一个NSURLProtocol,NSURLProtocol看起来像是一个协议,但其实这是一个类,而且必须使用该类的子类,并且需要被注册。
不管你是通过UIWebView, NSURLConnection 或者第三方库 (AFNetworking, MKNetworkKit等),他们都是基于NSURLConnection或者 NSURLSession实现的,因此你可以通过NSURLProtocol做自定义的操作。
1.创建子类,继承自NSURLProtocol
2.在ViewDidLoad或者application:didFinishLaunchingWithOptions中注册都可以(不需要的时候可以取消注册)
3.在自定义的NSURLProtocol中进行相关数据的缓存和处理,这里需要缓存的包括,response,request以及data
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//只处理http和https请求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;
}
return NO;
}
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return mutableReqeust;
}
+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
if ([request.URL host].length == 0) {
return request;
}
NSString *originUrlString = [request.URL absoluteString];
NSString *originHostString = [request.URL host];
NSRange hostRange = [originUrlString rangeOfString:originHostString];
if (hostRange.location == NSNotFound) {
return request;
}
//定向到bing搜索主页
NSString *ip = @"cn.bing.com";
// 替换域名
NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL *url = [NSURL URLWithString:urlString];
request.URL = url;
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//标示改request已经处理过了,防止无限循环
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
- (void)stopLoading
{
[self.connection cancel];
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
Tips:
1.需要的注意的是一个网页的缓存并不是只有一份,而是有许多份缓存,因为网页中的css文件,js文件都会被缓存下来,在后面的数据存储和删除时需要注意,另外当控制器传入的url和缓存的key不一样时,需要把之前的所有缓存都清除掉,再加入新的缓存。
2.每次只能只有一个protocol
进行处理,如果有多个自定义protocol
,系统将采取你registerClass
的倒序进行调用,一旦你需要对这个请求进行处理,那么接下来的所有相关操作都需要这个protocol
进行管理。
3.一定要注意标记请求,不然你会无限的循环下去。。。因为一旦你需要处理这个请求,那么系统会创建你这个protocol
的实例,然后你自己又开启了connection
进行请求的话,又会触发URL Loading system的回调。系统给我们提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;
这两个方法进行标记和区分。
上面的是OC版本的代码,原理其实都一样,Swift的代码我也会提供给大家,里面有具体的注释和代码。
https://github.com/kiddhmh/Simple-Demo
这是代码地址,打开运行后,关闭网络再次打开会加载缓存。如果帮助到你,还希望能给个star~