iOS: 聊聊 UIWebView 缓存

前言

在开发项目过程中, 一些若交互的页面会使用 HTML 展示.

在 iOS 中, 使用 UIWebView 的频率还是比较高的.

今天跟大家聊聊 UIWebView 缓存相关的话题.

准备工作

我今天使用 Tomcat 来作为 web 容器, 在本机搭建一个 web 服务器, 然后使用 iPhone 访问该 web 页面, 展示和梳理 UIWebview 关于缓存的问题.

如果你对 Tomcat 还不熟悉, 希望你可以先去大概了解一下, 如何在 Mac os 上面安装和使用 Tomcat, 可以参考我的博文: [Mac 配置 Tomcat8].

Tomcat 是一个开放源代码、运行 servlet 和 JSP Web 应用软件的基于 Java 的 Web 应用软件容器.
Tomcat Server 是根据 servlet 和 JSP 规范执行的,因此可以说 Tomcat Server 实行了 Apache-Jakarta 规范,且比绝大多数商业应用软件服务器要好.
但是 Tomcat 对静态文件、高并发的处理比较弱.

写这篇文章的时候, 我使用的版本分别是 apache-tomcat-8.5.8, jdk1.8.

配置 Tomcat

修改 server.xml 文件

文件在 Tomcat 的根目录的 conf 目录下, 如我的文件在这个目录:

1
apache-tomcat-8.5.8/conf/server.xml

增加如下内容:

1
2
3
4
<Host name="<your local ip>" debug="0" appBase="<base dir>" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
<Context path="" docBase="<html file path>" debug="0" reloadable="true" crossContext="true"/>
<Logger className="org.apache.catalina.logger.FileLogger" directory="logs" prefix="tot_log." suffix=".txt" timestamp="true"/>
</Host>

注意:
1.将上述内容放到 </Host></Engine> 节点中间.
2.将 name="<your local ip>" 中的 改为你本机的 ip 地址.
查看本机的 ip 地址方法很简单:

1
ifconfig | grep "inet " | grep -v 127.0.0.1

3.将 appBase=”“ 中的 改为你的 web 目录.
4.将 docBase=”“ 中的 改为你的 html 目录.

我的配置如下(部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
<!--mark 配置静态网页. [BEGIN] -->
<Host name="192.168.1.103" debug="0" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
<Context path="" docBase="test" debug="0" reloadable="true" crossContext="true"/>
<Logger className="org.apache.catalina.logger.FileLogger" directory="logs" prefix="tot_log." suffix=".txt" timestamp="true"/>
</Host>
<!--mark 配置静态网页. [END] -->
</Engine>
</Service>
</Server>

搞定上面的配置, 接下来可以配置相关目录了.

在 Tomcat 的根目录有个文件夹 webapps, 在 webapps 目录下新建目录 test 即可.

构建 HTML 页面

在 test 目录, 新建一个 html 文件

1
touch test.html

文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<p>
<em>Sample</em> text</p>
<p>
Now I input another line, with fancy<u><strong><em>styles</em></strong>
</u>.</p>
<p>
<em>Sample</em> text</p>
<p>
Now I input another line, with fancy <u><strong><em>styles</em></strong>
</u>.</p>
<p>
mark.zhang is itman.
</p>
<style>
.button {
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 86px;
margin: 50px 200px 100px 300px;
cursor: pointer;
border-radius: 15;
}
</style>
<p>
<button type="button" onclick="myFunction()" class="button">Try it</button>
</p>
<script>
function myFunction() {
alert("Blog: www.veryitman.com");
};
</script>

搭建完成后, 启动 Tomcat 服务器.

1
startup.sh

在浏览器里面通过 ip:port/test.html 的方式来访问该页面.

看到类似下面的效果即表示搭建成功:

1

客户端访问

客户端访问该页面, 使用 UIWebview 来请求(HTTP 协议)页面内容.

一般请求会使用下面的方法:

1
+ (instancetype)requestWithURL:(NSURL *)URL;

该方法的描述如下:

1
2
Creates and returns a URL request for a specified URL with default cache policy and timeout value.
The default cache policy is NSURLRequestUseProtocolCachePolicy and the default timeout interval is 60 seconds.

大概意思是使用的缓存策略是根据协议来的, 即 NSURLRequestUseProtocolCachePolicy. 超时时间默认是60s.

也就是说类似如下的请求:

1
NSURLRequest *urlReq = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.f];

如果协议支持缓存的话, UIWebview 请求到的数据就是缓存数据.该缓存是需要 WEB 服务器支持的.

这里我没有配置 Tomcat 的缓存.可以抓包看下:

2

后续博客会分别为大家介绍在 Tomcat 和 Nginx 配置缓存下, 客户端 UIWebview 请求的相关问题.

客户端显示页面效果:
2

ViewController 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#import "ViewController.h"
static NSString * const H5Url = @"http://192.168.1.104:8080/test.html";
@interface ViewController () <UIWebViewDelegate>
@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, strong) UIButton *refBtn;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_webView = [[UIWebView alloc] init];
CGSize boundsSize = self.view.bounds.size;
self.webView.frame = CGRectMake(0, 20, boundsSize.width, boundsSize.height);
self.webView.backgroundColor = [UIColor whiteColor];
self.webView.scrollView.showsHorizontalScrollIndicator = NO;
self.webView.scrollView.showsVerticalScrollIndicator = NO;
self.webView.scalesPageToFit = YES;
self.webView.delegate = self;
[self.view addSubview:self.webView];
UIButton *refreshBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.view addSubview:refreshBtn];
[refreshBtn addTarget:self action:@selector(onRefreshWebView) forControlEvents:UIControlEventTouchUpInside];
refreshBtn.backgroundColor = [UIColor redColor];
refreshBtn.layer.masksToBounds = YES;
refreshBtn.layer.cornerRadius = 5.f;
refreshBtn.frame = CGRectMake(50, 250, 200, 50);
[refreshBtn setTitle:@"刷新页面" forState:UIControlStateNormal];
_refBtn = refreshBtn;
[self loadDataUsingCache];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(@"didFailLoadWithError: %@", error);
[self hideLoading];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(@"shouldStartLoadWithRequest: %@", request);
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
NSLog(@"webViewDidStartLoad");
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(@"webViewDidFinishLoad");
[self hideLoading];
}
//刷新页面.
- (void)onRefreshWebView
{
// 方式1: 不使用缓存请求数据
//[self loadDataNoUsingCache];
// 方式2: 清除 NSCache 缓存, 再请求数据
[self clearAllCache];
[self loadDataUsingCache];
}
- (void)loadDataUsingCache
{
[self showLoading];
NSURL *url = [NSURL URLWithString:H5Url];
NSURLRequest *urlReq = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataDontLoad
timeoutInterval:10.f];
[self.webView loadRequest:urlReq];
}
- (void)loadDataWithProtocol
{
[self showLoading];
NSURL *url = [NSURL URLWithString:H5Url];
[NSURLRequest requestWithURL:url];
NSURLRequest *urlReq = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.f];
[self.webView loadRequest:urlReq];
}
- (void)loadDataNoUsingCache
{
[self showLoading];
NSURL *url = [NSURL URLWithString:H5Url];
NSURLRequest *urlReq = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:20.0];
[self.webView loadRequest:urlReq];
}
- (void)clearAllCache
{
// remove cache rsp
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[[NSURLCache sharedURLCache] setDiskCapacity:0];
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
}
- (void)showLoading
{
[self.refBtn setTitle:@"刷新中..." forState:UIControlStateNormal];
}
- (void)hideLoading
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.refBtn setTitle:@"刷新页面" forState:UIControlStateNormal];
});
}
@end

这里注意下面的 三个方法 :

1
2
3
4
5
6
7
8
9
// 使用缓存数据, 如果有缓存的话
// 使用这个方法, 改变 HTML 或者 JS 代码
// 页面不会拉取最新数据, 还是使用之前请求到的数据.
// 除非重新刷新
- (void)loadDataUsingCache;
// 使用协议缓存, 需要 web 服务器支持.
- (void)loadDataWithProtocol;
// 不使用缓存, 加载数据
- (void)loadDataNoUsingCache;

另外, 刷新 UIWebview 的方式如下, 有 两种方式 来刷新页面:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 刷新页面.
*/
- (void)onRefreshWebView
{
// 方式1: 不使用缓存请求数据
//[self loadDataNoUsingCache];
// 方式2: 清除 NSCache 缓存, 再请求数据
[self clearAllCache];
[self loadDataUsingCache];
}

这种刷新方式, 会重新加载数据.
但是不适合多层级的 HTML 页面, 比如你的 HTML 页面有很多层, 想刷新当前页面, 可以使用下面的方式:

1
2
// 重新加载当前页面
[self.webView reload];

附加

查看本机 IP 的 shell

1
2
#!/bin/sh
ifconfig | grep "inet " | grep -v 127.0.0.1

停止 Tomcat 的运行

1
shutdown.sh
坚持原创技术分享!