使用NSURLProtocol实现离线缓存
一、说明:
NSURLProtocol可以拦截任何网络请求,包含UIWebView中发出的所有请求。但是在WKWebView中,只能拦截到最初始的请求,内嵌的资源下载拦截不到。比如通过WKWebView加载"http://www.baidu.com",则只能拦截到"http://www.baidu.com",网页内部的资源加载拦截不到。页面跳转属于最初始请求之内,可以拦截到。
二、创建NSURLProtocol的子类,通过下面的代码注册此协议类:
[NSURLProtocol registerClass:[MyURLProtocol class]];
三、下面是此子类的代码:
#import "MyURLProtocol.h"
#define MyURLProtocolHandled @"MyURLProtocolHandled"
//创建archive数据模型,重写编码解码协议
@interface MyCacheData : NSObject
@property(nonatomic,strong) NSURLRequest *request;
@property(nonatomic,strong) NSURLResponse *response;
@property(nonatomic,strong) NSData *data;
@end
@interface NSURLRequest (MutableCopyWorkaround)
- (id)mutableCopyWorkaround;
@end
@interface MyURLProtocol ()
@property(nonatomic,strong) NSURLConnection *connection;
@property(nonatomic,strong) NSMutableData *httpData;
@property(nonatomic,strong) NSURLResponse *response;
@end
@implementation MyURLProtocol
#pragma mark - 重写NSURLProtocol子类方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//如果此请求是拦截到请求之后,接管请求而发起的新请求,则不处理。
if ([request.URL.scheme isEqualToString:@"http"] &&
[request valueForHTTPHeaderField:MyURLProtocolHandled] == nil)
{
return YES;
}
return NO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a
toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
//如果发现已经存在此请求的缓存数据,则返回缓存数据,否则发起新的请求从服务求加载数据
MyCacheData *cacheData = [NSKeyedUnarchiver unarchiveObjectWithFile:
[self cachePathForRequest:self.request]];
if(cacheData != nil)
{
NSData *data = cacheData.data;
NSURLResponse *response = cacheData.response;
NSURLRequest *redirectRequest = cacheData.request;
//使用NSURLProtocolClient做请求转向,直接将请求和数据转发到之前的请求
if(redirectRequest != nil)
{
[[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest
redirectResponse:response];
}
else
{
[[self client] URLProtocol:self didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
}
else
{
//接管此网络请求,发起一个新的请求,后续会将新请求拿到的数据交给之前的旧请求
NSMutableURLRequest *connectionRequest = [[self request] mutableCopyWorkaround];
//增加标记,标示是由我们接管而发出的请求
[connectionRequest setValue:@"Tag" forHTTPHeaderField:MyURLProtocolHandled];
self.connection = [NSURLConnection connectionWithRequest:connectionRequest
delegate:self];
}
}
- (void)stopLoading
{
[self.connection cancel];
self.connection = nil;
}
#pragma mark - 网络请求代理
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response
{
if(response != nil)
{
NSMutableURLRequest *redirectableRequest = [request mutableCopyWorkaround];
//缓存数据
MyCacheData *cacheData = [MyCacheData new];
[cacheData setData:self.httpData];
[cacheData setResponse:response];
[cacheData setRequest:redirectableRequest];
[NSKeyedArchiver archiveRootObject:cacheData
toFile:[self cachePathForRequest:[self request]]];
//将请求和缓存的响应数据转向到之前的请求
[[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest
redirectResponse:response];
return redirectableRequest ;
}
return request;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
return YES;
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection
didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
//保存响应对象
self.response = response;
[self.client URLProtocol:self didReceiveResponse:response
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[self.client URLProtocol:self didLoadData:data];
//保存服务器返回的数据
if(self.httpData == nil) {
self.httpData = [NSMutableData dataWithData: data];
}
else
{
[self.httpData appendData:data];
}
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.client URLProtocolDidFinishLoading:self];
//请求加载完毕之后,将数据缓存
MyCacheData *cacheData = [MyCacheData new];
[cacheData setData:self.httpData];
[cacheData setResponse:self.response];
[NSKeyedArchiver archiveRootObject:cacheData
toFile:[self cachePathForRequest:self.request]];
self.connection = nil;
self.httpData = nil;
self.response = nil;
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[self.client URLProtocol:self didFailWithError:error];
self.connection = nil;
self.httpData = nil;
self.response = nil;
}
#pragma mark - 为请求创建缓存路径
- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest
{
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSUserDomainMask, YES) lastObject];
return [cachesPath stringByAppendingPathComponent:
[NSString stringWithFormat:@"%ld", [[[aRequest URL] absoluteString] hash]]];
}
@end
@implementation NSURLRequest (MutableCopyWorkaround)
- (id) mutableCopyWorkaround {
NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc]
initWithURL:[self URL]
cachePolicy:[self cachePolicy]
timeoutInterval:[self timeoutInterval]];
[mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]];
return mutableURLRequest;
}
@end
@implementation MyCacheData
-(id) initWithCoder:(NSCoder *) aDecoder
{
self = [super init];
if(!self) {
return nil;
}
[self setData:[aDecoder decodeObjectForKey:@"data"]];
[self setRequest:[aDecoder decodeObjectForKey:@"request"]];
[self setResponse:[aDecoder decodeObjectForKey:@"response"]];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:[self data] forKey:@"data"];
[aCoder encodeObject:[self request] forKey:@"request"];
[aCoder encodeObject:[self response] forKey:@"response"];
}
@end
部分代码转自:http://tanlimin201.blog.163.com/blog/static/38171407201383032914736/
使用NSURLProtocol实现离线缓存相关推荐
- 使用NSURLProtocol实现UIWebView的离线缓存的简单实现
文章介绍了使用NSURLProtocol实现UIWebView的离线缓存的简单实现,你可以在github上下载这个demo的代码. 无论是"MKNetworkKit"还是" ...
- 离线缓存之RNCachingURLProtocol解析
主要功能:实现 网页离线加载.基本思想来源于AFCache.作者是Rob Napier(IOSX Programming的作者). 使用方法: To build, you will need the ...
- 源码推荐:移动端商城(微信小程序源代码) WebView离线缓存
移动端商城(微信小程序源代码)(上传者:腾讯攻城师jack) 功能包括:商品橱窗,商品搜索,购物车,结账等功能. TableView嵌套webView自适应高度(上传者:linlinchen) tab ...
- 关于域用户的离线缓存登录知识分享
关于域用户的离线缓存登录知识分享 微软设计缓存登录的目的? 缓存登录主要是为了解决当公司域控制器发生故障联系不上DC或用户拿笔记本电脑回家不拔×××的情况下,依然能够登录到系统,进行办公.如果用户登录 ...
- 离线缓存占内存吗_彻底弄懂浏览器缓存策略
浏览器缓存策略对于前端开发同学来说不陌生,大家都有一定的了解,但如果没有系统的归纳总结,可能三言两语很难说明白,甚至说错,尤其在面试过程中感触颇深,很多候选人对这类基础知识竟然都是一知半解,说出几个概 ...
- html开启页面离线缓存,HTML5 离线缓存
离线缓存 applicationCache 第一次加载后将数据缓存,如果没有清除缓存,下一次没有网络也能加载. 使用 1. 使用 manifest 属性,引入 .appcache 文件 每个指定了 m ...
- html5 客户端数据缓存机制,深入理解HTML5离线缓存机制
TML5提供了一种离线应用缓存机制,使得网页应用可以离线使用,这种机制在移动端浏览器上支持度非常广,所有版本的android和ios浏览器都能很好的支持.我们可以放心的使用该特性来加速移动端页面的访问 ...
- javascript中本地储存、离线缓存、地理定位、网络状态
本地储存: 实际开发中某些内容是不需要放到服务器中,而是放到了浏览器中,需要的时候可以快速的访问,甚至页面刷新也可能不会丢失数据,容量较大:这里介绍两种数据存储方式:sessionStorage约5M ...
- 使用 jQuery Mobile 与 HTML5 开发 Web App (十七) —— HTML5 离线缓存
本文要介绍的,是 HTML5 离线网络应用程序的特性,离线网络应用程序在 W3C 中的实际名称是 "Offline Web applications" ,也称离线缓存.当用户打开浏 ...
最新文章
- centos7 systemctl 管理 mysql
- Silverlight for Windows Phone 7开发系列(2):第一个Silverlight程序
- Visual Studio 2008 当页面进行调试时,IE8浏览器显示的是空白页面?
- java和tornado_TornadoJ
- 那是两个小时我不会回来
- python.day05
- BZOJ 1270: [BeijingWc2008]雷涛的小猫( dp )
- 如何使用git命令行上传项目到github
- Problem C: 01字串
- 【线性代数本质】1:向量究竟是什么
- Java Persistence with MyBatis 3(中国版)
- Tuxedo 8.110gR3 开发环境的安装与配置
- 库、教程、论文实现,这是一份超全的PyTorch资源列表(Github 2.2K星)
- 开启Mac原生NTFS支持
- DarkAngels勒索病毒分析
- python3的字符串操作
- mysql分页查询如何优化_mysql分页查询优化
- python 实现改变excel文件列宽
- mysql 数据表格切分_MySQL数据库垂直和水平切分
- 手把手教你如何在AWS EC2 启用 IPv6
热门文章
- LeetCode 206 Reverse Linked List 解题报告
- sql注入基于错误-单引号-字符型
- 模拟 Codeforces Round #288 (Div. 2) A. Pasha and Pixels
- [LeetCode] Plus One
- Innodb 表空间卸载、迁移、装载
- Ubuntu桌面培训(Ubuntu Desktop Course)中文译本发布
- linux网络编程——boa移植
- CentOS7.7安装MySQL5.6并配置环境变量(详细版)
- 如何获取filecoin_获得Filecoin币有哪些条件?
- ios笔试题算法_微软笔试题-Dijkstra算法