我是如何一步一步实现网页离线缓存的?

问题

一个Hybrid APP,如何做离线缓存策略?也可以简单来说,你的APP只是一个壳,里面真正加载的内容是H5,如果优化加载内容的速度?

先了解一下NSURLProtocol

从字面意思看它是一个协议,但是它其实是一个类,而且继承自NSObject。它的作用是处理特定URL协议的加载。它本身是一个抽象类,提供了使用特性URL方案处理URL的基础结构。你可以自己创建NSURLProtocol的子类,来让自己的应用支持自定义的协议或者URL方案。

应用程序永远不需要直接实例化一个NSURLProtocol子类。当一个下载开始的时候,系统创建一个合适的protoco对象来响应URL请求。你要做的就是自己定义一个你自己的protocol,然后在APP启动的时候调用registerClass:,让系统知道你的协议。

这里需要注意:你不能在watchOS 2以及更高版本中自定义URL scheme和协议。

为了支持特定的自定义请求,你最好定义NSURLRequest 或者NSMutableURLRequest。让自定义的这些对象来实现请求,这里需要使用NSURLProtocol的propertyForKey:inRequest:和setProperty:forKey:inRequest,然后你可以自定义NSURLResponse类来模拟返回信息。

接下来就开始对UIWebView进行离线缓存处理。

UIWebView的离线缓存处理

首先,我们需要自定义一个NSURLProtocol的子类,并且在AppDelegate.m的

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [NSURLProtocol registerClass:[ZGURLProtocol class]];
    return YES;
}

注册。接下来的所有操作就都是在我们自定义的ZGURLProtocol中操作了。先看一下registerClass的作用:

尝试注册一个NSURLProtocol的子类,使其对URL Loading System可见。这里的URL Loading System就是一组类和协议,允许你的应用程序访问由URL产生的内容,比如请求、接收内容和Cache等。当URL Load System开始加载一个请求的时候,每个注册的协议类都被依次去调用,以确定是否可以用指定的请求去初始化它。首先被调用的方法是:

+ (BOOL)canInitWithRequest:(NSURLRequest *)request;

在该方法里面进行缓存过滤,比如你想只缓存js,那么判断request的path的后缀,如果是js,就返回YES,否则返回NO。

如果返回YES,那么就相当于该请求被自定义的URLProtocol来处理,这里不能保证所有的注册的NSURLProtocol都能被处理到。如果你定义了多个NSProtocol子类,这些子类将会以相反的顺序调用。也就是说如果你是这样写的:

 [NSURLProtocol registerClass:[ZGURLProtocol class]];
 [NSURLProtocol registerClass:[ZProtocol class]];

那么最先执行的是ZProtocol,如果参initWithRequest:返回的为YES,则请求由ZProtocol进行处理,且不会再走ZGURLProtocol。如果ZProtocol的initWithRequest:返回的为NO,则请求继续向下传递由其他的NSURLProtocol子类处理。

一旦返回YES,那么请求将会由自己写的子类处理,首先会调用:

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

这个是一个抽象的方法,子类必须对其实现。通常情况下,我们一般都是直接返回request,但是这里你也可以直接修改此request,包括header,hosts等。可以对指定request进行重定向操作。

在这里,我们只是将现有的request进行返回即可。

紧接着,便会开始请求:

- (void)startLoading;

该方法的作用就是开始请求protocol指定的请求。该方法也是protocol子类必须实现的方法。在这里所做的操作就是:

先判断是否有缓存数据,如果有,则自己创建NSURLResponse,然后将缓存数据放入,并进行client的一些操作,然后返回;如果没有缓存数据,则新建一个NSURLConnection,然后发送请求。

先说一下有缓存的情况下:

if (model.data && model.MIMEType) {
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:model.MIMEType expectedContentLength:model.data.length textEncodingName:nil];
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
        [self.client URLProtocol:self didLoadData:model.data];
        [self.client URLProtocolDidFinishLoading:self];
        return;
    }

(model是缓存数据)有缓存的情况下,直接使用缓存的数据和MIME类型,然后构建NSURLResponse,然后通过协议client调用代理方法。这里的client是一个protocol,如下:

@protocol NSURLProtocolClient <NSObject>

- (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;

@end

该协议提供了NSURLProtocol子类与URL Loading System进行沟通的接口。一个APP一定不要去实现这个协议。有缓存的情况下调用回调方法,然后进行处理。

在没有缓存的情况下:

实例化一个connection,然后发起请求。在我们收到response的时候:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.responseData = [[NSMutableData alloc] init];
    self.responseMIMEType = response.MIMEType;
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

紧接着就是接收数据:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.responseData appendData:data];
    [self.client URLProtocol:self didLoadData:data];
}

接收完数据之后便调用了:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    ZGCacheModel *model = [ZGCacheModel new];
    model.data = self.responseData;
    model.MIMEType = self.responseMIMEType;
    [self setMiType:model.MIMEType withKey:[self.request.URL path]];//userdefault存储MIMEtype

    [[ZGUIWebViewCache sharedWebViewCache] setCacheWithKey:self.request.URL.absoluteString value:model];

    [self.client URLProtocolDidFinishLoading:self];
}

这个方法是结束家在之后的调用,我们需要在这里将请求过来的数据进行缓存。这样我们本地就有了指定URL的返回数据。

这里还有一个重要的东西没有介绍,那就是

[NSURLProtocol propertyForKey:ZGURLProtocolKey inRequest:request]
[NSURLProtocol setProperty:@YES forKey:ZGURLProtocolKey inRequest:mutableRequest];

这里的

+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;

作用是在指定的请求中设置与特定的键值相关联。防止多次调用一个request。

这样,我们就完成了UIWebView的离线缓存。在这里我封装了一个ZGUIWebViewCache。感兴趣的可以看一下。

WKWebView的离线缓存处理

WKWebView离线缓存和UIWebView缓存类似,只不过使用WKWebView除了一开始调用一下NSURLProtocol的canInitWithRequest:方法之后,之后的请求似乎就和NSURLProtocol完全无关了,网上都说WKWebView的请求是在独立的进程里,所以不走NSURLProtocol。这里是通过NSURLProtocol+WKWebView类进行处理的,详情可参见:ZGWKWebViewCache

剩下的处理过程就和UIWebView缓存处理类似了。

以上便是对网页离线缓存的实现。

如有问题,欢迎留言沟通!

参考

1.让 WKWebView 支持 NSURLProtocol

时间: 2024-10-13 08:01:23

我是如何一步一步实现网页离线缓存的?的相关文章

大流量网站性能优化:一步一步打造一个适合自己的BigRender插件(转)

BigRender 当一个网站越来越庞大,加载速度越来越慢的时候,开发者们不得不对其进行优化,谁愿意访问一个需要等待 10 秒,20 秒才能出现的网页呢? 常见的也是相对简单易行的一个优化方案是 图片的延迟加载.一个庞大的页面,有时我们并不会滚动去看下面的内容,这样就浪费了非首屏部分的渲染,而这些无用的渲染,不仅包括图片,还包括其他的 DOM 元素,甚至一些 js/css(某些js/css 是根据模块请求的,比如一些 ajax),理论上,每增加一个 DOM,都会增加渲染的时间.有没有办法能使得

一步一步入门机器学习之五:机器学习自学指南

事实上有许多的途径可以了解机器学习,也有许多的资源例如书籍.公开课等可为所用,一些相关的比赛和工具也是你了解这个领域的好帮手.本文我将围绕这个话题,给出一些总结性的认识,并为你由程序员到机器学习高手的蜕变旅程中提供一些学习指引. 机器学习的四个层次 根据能力可以将学习过程分成四个阶段.这也是一个有助于我们将所有学习资源进行分类的好方法. 初学阶段 新手阶段 中级阶段 高级阶段 我之所以把初学阶段和新手阶段区分开来,是因为我想让那些完全初学者(对这个领域感兴趣的程序员)在初学阶段对机器学习有一个大

loadrunner安装运行一步一步来(多图)

安装loadrunner 一路遇到很多坑,很多坑,坑,为什么呢? 因为这软件是收费的,他操作文档写的很详细,就是不写基础环境的配置,下面安装过程写详细一些,减少大家没必要时间上的浪费和对此的谩骂 现在loadrunner 12的版本已经出来了,不过还没有破解,所以先安装测试11的版本,不绕圈子,先下载, 链接: http://pan.baidu.com/s/1kT8CbVh 密码: v4br 加密码是怕被删 遇到的坑 下面是通用的安装说明: 1.下载loadrunner-11.zip文件,解压缩

一步一步教你分享开源项目到 Maven 中心仓库

欢迎各位关注我的新浪微博:http://weibo.com/kifile 转载请标明出处(http://blog.csdn.net/kifile) 相信很多程序猿朋友都或多或少写过一些通用库文件,也有很多人会秉着开源的精神将代码分享到类似 Github 之类的代码托管网站去. 其实大家分享代码的时候,目的在于让更多的人使用,但是如果仅仅是放在 Github 这些工具上等人家下载使用,其实很多人是会觉得很麻烦的,那么有什么方法能够帮助我们在不下载 Github 上的源码的时候,使用这些代码吗? 回

一步一步实战HTML音乐播放器

在这里我用HTML5从头开始一步一步来制作一个简约的音乐播放器,大家可以参考一下,接下来正式开始. 音乐播放器效果 播放器分析 这里将播放器分两块来做: 视图层(html + css) 逻辑层 ( js ) 视图层分析 视图中包含: 播放器容器 播放器名称 音乐专辑图 音乐信息 歌曲名 歌手 专辑名 控制区 上一曲 播放 下一曲 播放进度条 播放时间 当前时间 歌曲总时间 音频控件 页面背景 逻辑层分析 逻辑层处理包括: 加载歌单 渲染歌曲信息 专辑图 歌曲名 歌手 专辑名 歌曲时长 歌曲音频地

一步一步搭建Svn+Apache环境(附源码包)

一步一步搭建Svn+Apache环境(附源码包) 看了很多网上的搭建方法,但是因为配置的svn和Apache的版本问题和网上写的教程的不完整等原因,阻碍了很多想要学习的朋友!!今天亲手搭建了一下Svn+Apache环境,并且详细记录了每一步搭建的细节,送给需要的朋友们! 软件版本: redhat6.4(64位) apr-1.5.1.tar.gz apr-util-1.5.3.tar.gz httpd-2.2.27.tar.gz sqlite-amalgamation-3.6.17.tar.gz

Rhythmk 一步一步学 JAVA (22) JAVA 网络编程

1.获取主机信息 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test     public void GetDomainInfo() throws UnknownHostException {         String domain = "www.baidu.com";         InetAddress netAddress = InetAddress.getByName(domain);         // 获取

jQuery一步一步实现跨浏览器的可编辑表格,支持IE、Firefox、Safari、

脚 本 之 家 www.jb51.net 脚本云 专题 素材下载 电子书 软件下载 源码下载 服务器常用软件 a5交易 首页 网页制作 脚本专栏 脚本下载 网络编程 数据库 CMS教程 电子书籍 平面设计 媒体动画 操作系统 网站运营 网络安全 YUI.Ext相关 prototype jquery dojo json lib_js js面向对象 extjs Mootools Seajs 其它 特色栏目: vbscript 正则表达式 javascript 批处理 服务器软件 素材下载 低价出售流

一步一步用jenkins,ansible,supervisor打造一个web构建发布系统

新blog地址:http://hengyunabc.github.io/deploy-system-build-with-jenkins-ansible-supervisor/ 一步一步用jenkins,ansible,supervisor打造一个web构建发布系统. 本来应该还有gitlab这一环节的,但是感觉加上,内容会增加很多.所以直接用github上的spring-mvc-showcase项目来做演示. https://github.com/spring-projects/spring-