一、初始化
1.initWithFrame:configuration
self.wkWebView = [[WKWebView alloc] initWithFrame:frame configuration:[self _defaultConfiguration]];
2.WKWebViewConfiguration类说明
wkwebview初始化时的参数配置
websiteDataStore
wkwebview的存储空间,一般是处理cookie,缓存等浏览器相关的临时存储
读取cookie代码
[config.websiteDataStore fetchDataRecordsOfTypes:[NSSet setWithObject:WKWebsiteDataTypeCookies] completionHandler:^(NSArray *records) {}];
清理所有存储(allWebsiteDataTypes)
WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
[dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
completionHandler:^(NSArray * __nonnull records) {
for (WKWebsiteDataRecord *record in records) {
[[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
forDataRecords:@[record]
completionHandler:^{
NSLog(@"Cookies for %@ deleted successfully",record.displayName);
}];
}
}];
allowsInlineMediaPlayback
PS: 视频播放器不全屏显示 , iOS 10 以下使用 webkit-playsinline 属性
processPool
就是一个处理池,打开一个webview可以指定从什么池子里打开,一般用默认或者指定一个单例WKProcessPool就行了
applicationNameForUserAgent
可以指定userAgent中的application的名字,如果要修改整个UA,需要采用全局设置
mediaTypesRequiringUserActionForPlayback(iOS10+)/mediaPlaybackRequiresUserAction(iOS10-)
是否自动播放视频
if (@available(iOS 10.0, *)) {
config.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone;
}else {
config.mediaPlaybackRequiresUserAction = NO;
}
preferences
WKPreferences的配置
其它参数
js注入,创建js句柄(bridge)等在后续js通信处介绍
3.WKPreferences类说明
WKWebViewConfiguration另外的一些属性配置
javaScriptEnabled
是否支持js,如果是no,html加载时候直接忽略js的加载
KVC设置 allowFileAccessFromFileURLs
是否允许file路径
[prefs setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
4.WKUIDelegate
wkwebview.UIDelegate属性
用户js中调用alert,confirm,prompt,如果不适配则无法使用对应js功能,估计是安全问题,因为使用中有的会采用这个作为bridge桥接
5.WKNavigationDelegate
wkwebview.navigationDelegate属性
监听wkwebview整个生命周期的代理方法,详细见"二、生命周期方法"
二、生命周期方法(WKNavigationDelegate)
1.请求前决定是否要跳转
用户点击网页上的链接,打开新页面时,调用。
为了兼容iOS8的js通信,也可以在这里拦截url做bridge分发
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
BOOL isContinueRequest = YES;
// NSString *jsNotId = [self __getJSNotificationId:[navigationAction.request URL]];
// NSString *urlStr = [navigationAction.request URL].absoluteString;
// if (jsNotId) {
// // 符合 js to native 的方法
// isRequest = NO;
// [self handleJSBridgeGetJsonStringForJsNotId:jsNotId];
// }else if ([urlStr hasPrefix:@"ios://"]) {
// // 特殊host拦截
// isRequest = NO;
// [self handleSpecialJSBridgeTask:urlStr];
// }
if ([self.ArleneWebViewDelegate respondsToSelector:@selector(ArleneWebView:shouldStartLoadWithRequest:navigationType:)]) {
isContinueRequest = [self.ArleneWebViewDelegate ArleneWebView:webView shouldStartLoadWithRequest:navigationAction.request navigationType:UIWebViewNavigationTypeOther];
}
if (isContinueRequest) {//允许
decisionHandler(WKNavigationActionPolicyAllow);
} else {//不允许跳转
decisionHandler(WKNavigationActionPolicyCancel);
}
}
2.页面开始请求
正式发送请求前的回调,无法拦截,可以在这个点注入一些自己的js
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
ALLOG(@"webView->didStartProvisionalNavigation:");
[self importJS];
if([self.ArleneWebViewDelegate respondsToSelector:@selector(ArleneWebViewDidStartLoad:)]){
[self.ArleneWebViewDelegate ArleneWebViewDidStartLoad:webView];
}
}
3.收到响应后决定是否跳转
- (void) webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
ALLOG(@"webView->收到请求后 3 decidePolicyForNavigationResponse:");
if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
NSInteger statusCode =response.statusCode;
NSString * urlStr = response.URL.absoluteString;
ALLOGF(@"当前状态值:%ld;当前跳转地址:%@",statusCode,urlStr);
}
//允许跳转
decisionHandler(WKNavigationResponsePolicyAllow);
//不允许跳转
//decisionHandler(WKNavigationResponsePolicyCancel);
}
4-1.加载完成
回调该函数未必就代表了成功
回调该函数未必就代表了成功
回调该函数未必就代表了成功
如果访问的页面服务器出错(返回500,400等非200的statusCode),这个方法也会被回调
//读取成功
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
ALLOG(@"webView->didFinishNavigation:");
[self __ArleneWebViewDidFinishLoad];
if([self.ArleneWebViewDelegate respondsToSelector:@selector(ArleneWebViewDidFinishLoad:)]){
[self.ArleneWebViewDelegate ArleneWebViewDidFinishLoad:webView];
}
if([self.ArleneWebViewDelegate respondsToSelector:@selector(ArleneWebViewAllFinishLoad:)]){
[self.ArleneWebViewDelegate ArleneWebViewAllFinishLoad:webView];
}
}
4-2.加载失败
2种请求错误:
- 在“页面开始请求”后 “收到请求响应”前的错误
比如:地址非法,DNS解析地址有问题,本地网络问题
总之是还没有请求到服务器时候的错误,都会返回在这里
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation
withError:(NSError *)error {
ALLOG(@"webView->didFailProvisionalNavigation:");
[self __ArleneWebView:webView didFailLoadWithError:error];
}
打印的日志
2020-06-04 14:09:33.416181+0800 ArleneiOS[7346:272402] -[ArleneWebView webView:decidePolicyForNavigationAction:decisionHandler:] [Line 551] webView->请求前 1 decidePolicyForNavigationAction:http://i.arlene.coms:3333/
2020-06-04 14:09:33.423342+0800 ArleneiOS[7346:272402] webView->开始请求页面 2 didStartProvisionalNavigation:
2020-06-04 14:09:37.021316+0800 ArleneiOS[7346:272402] webView->didFailProvisionalNavigation:
- 在请求页面过程中的错误
服务器接收到请求,并开始返回数据给到客户端的过程中出现传输错误
这个错误不是返回500,400等非200错误的回调
这个错误不是返回500,400等非200错误的回调
这个错误不是返回500,400等非200错误的回调
重要的事情说三遍
实际表现的错误可能是你传输过程中,断网了或者服务器down掉了导致的错误
//地址正确,返回的response有问题
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation
withError:(NSError *)error {
ALLOG(@"webView->didFailNavigation:");
[self __ArleneWebView:webView didFailLoadWithError:error];
}
打印的日志
2020-06-04 14:06:10.950200+0800 ArleneiOS[7273:268811] -[ArleneWebView webView:decidePolicyForNavigationAction:decisionHandler:] [Line 551] webView->请求前 1 decidePolicyForNavigationAction:http://i.arlene.com:3333/
2020-06-04 14:06:10.956527+0800 ArleneiOS[7273:268811] webView->开始请求页面 2 didStartProvisionalNavigation:
2020-06-04 14:06:11.590449+0800 ArleneiOS[7273:268811] webView->收到请求后 3 decidePolicyForNavigationResponse:
2020-06-04 14:06:11.592887+0800 ArleneiOS[7273:268811] webView->内容开始返回 4 didCommitNavigation:
2020-06-04 14:06:48.776484+0800 ArleneiOS[7273:268811] webView->didFailNavigation:
5.安全验证/证书验证
对访问网站的证书做验证,并决定是否拦截
实际应用过程中由于涉及到第三方合作,所以基本采用全部放过+url白名单方式做控制
如果需要对证书做强校验,可以采用AFNetwork的认证证书方式做比对
// 如果需要证书验证,与使用AFN进行HTTPS证书验证是一样的
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler{
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
NSURLCredential *card = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,card);
}
}
其它
不常用的说明如下
@protocol WKNavigationDelegate
@optional
// 主机地址被重定向时调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
//9.0才能使用,web内容处理中断时会触发
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
@end
三、访问页面
请求方式
1.请求在线页面
//1.创建request
NSString* urlString = @"https://www.baidu.com";
NSURL* url=[NSURL URLWithString:urlString];
NSURLRequestCachePolicy cachePolicy = NSURLRequestUseProtocolCachePolicy;
NSURLRequest *request =[NSURLRequest requestWithURL:url
cachePolicy:cachePolicy
timeoutInterval:0];
//2.请求
[self.wkWebView loadRequest:request];
如果需要自定义header,可以采用NSMutableURLRequest,然后设置
[mutableRequest addValue:cid?:@"" forHTTPHeaderField:@"x-c-id"];
2.请求沙盒页面
请求本地沙盒里的页面,主要是拼对URL就行了
注意url的头部是“file:///”注意“斜杠”的数量是3个
或者直接使用
NSURL *fileURL = [NSURL fileURLWithPath:path];
NSURLRequest *request =[NSURLRequest requestWithURL:url cachePolicy:cachePolicy timeoutInterval:0];
然后发起请求
[self.wkWebView loadFileURL:request.URL allowingReadAccessToURL:[request.URL URLByDeletingLastPathComponent]]
PS:我发现在iOS13+模拟器上,直接用loadRequest也可以访问本地沙盒,并没有权限问题,但是为了减少兼容问题,还是选择使用本地读取
3.请求内置包(bundle)页面
内置包就是bundle包,就是将bundle包路径拼接好,然后请求沙盒方式读取页面
自定义了一个url头部"bundle://",在请求的时候做"file:///"头部替换
4.加载源代码
直接把html文件读出来以后,以页面内容方式去读取
[self.wkWebView loadHTMLString:htmlString baseURL:nil];
5.离线资源包的一点思考
利用离线加载这一特性,我们可以通过服务端资源打包成本地资源包(zip包),通过服务器比对方式下载资源包,解压后放在本地指定的沙盒目录,随后通过wkwebview加载本地方式打开页面。
对于资源包要求
- 前后端分离(目前前端基本如此)
- 资源包加载需要相对路径,大部分在线资源都是通过cdn的,如何通过cdn去转换成资源包并打包进来,也是一个挑战,或者直接用cdn包也是可以的
- 要考虑降级策略,如果加载失败,资源包出现问题,如何快速替换最新资源包或者回滚。
缓存策略
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0, // 默认策略,具体的缓存逻辑和协议的声明有关,如果协议没有声明,不需要每次重新验证cache。
NSURLRequestReloadIgnoringLocalCacheData = 1, // 忽略本地缓存,直接从后台请求数据
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // 忽略本地缓存数据、代理和其他中介的缓存,直接从后台请求数据
NSURLRequestReturnCacheDataElseLoad = 2, // // 优先从本地拿数据,且忽略请求生命时长和过期时间。但是如果没有本地cache,则请求源数据
NSURLRequestReturnCacheDataDontLoad = 3, //只从本地拿数据 离线模式
NSURLRequestReloadRevalidatingCacheData = 5, // 从原始地址确认缓存数据的合法性后,缓存数据就可以使用,否则从原始地址加载。
};
1.默认策略NSURLRequestUseProtocolCachePolicy
遵循web的缓存策略,简单介绍:
分为两种缓存
1.对比缓存 (服务器方式比对,304)
需要和服务器做一次比对,但是不会拿回所有数据,所以请求快且轻。
Etag / If-None-Match :返回Etag给到客户端,下次请求时header中将etag的值设置在If-None-Match 服务器做比对后客户端比较后,决策是否缓存
Last-Modified / If-Modified-Since:原理类似上面,只不过是用时间的新旧来决策缓存
2.强缓存 (本地缓存,200 from memory cache/from disk cache)
Expires(1.0产物,基本可以忽略) 第一次请求返回一个head,值是一个时间点,下次如果再请求相同资源,判断时间是否过期,如果未过期则命中缓存
Cache-Control,主要指定max-age={xxx sencods}
2.NSURLRequestReloadIgnoringLocalCacheData
忽略所有缓存,建议本地加载可以采取这种方式,忽略缓存,因为缓存空间是有限的,不要影响真正需要缓存的页面