一、初始化

1.initWithFrame:configuration

self.wkWebView = [[WKWebView alloc] initWithFrame:frame configuration:[self _defaultConfiguration]];

2.WKWebViewConfiguration类说明

wkwebview初始化时的参数配置

websiteDataStore

wkwebview的存储空间,一般是处理cookie,缓存等浏览器相关的临时存储

读取cookie代码

[config.websiteDataStore fetchDataRecordsOfTypes:[NSSet<NSString *> setWithObject:WKWebsiteDataTypeCookies] completionHandler:^(NSArray<WKWebsiteDataRecord *> *records) {}];    

清理所有存储(allWebsiteDataTypes)

WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];[dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]           completionHandler:^(NSArray<WKWebsiteDataRecord *> * __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种请求错误:

  1. 在“页面开始请求”后 “收到请求响应”前的错误

比如:地址非法,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:
  1. 在请求页面过程中的错误

    服务器接收到请求,并开始返回数据给到客户端的过程中出现传输错误

    这个错误不是返回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 <NSObject>@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.创建requestNSString* 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加载本地方式打开页面。

对于资源包要求

  1. 前后端分离(目前前端基本如此)
  2. 资源包加载需要相对路径,大部分在线资源都是通过cdn的,如何通过cdn去转换成资源包并打包进来,也是一个挑战,或者直接用cdn包也是可以的
  3. 要考虑降级策略,如果加载失败,资源包出现问题,如何快速替换最新资源包或者回滚。

缓存策略

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

忽略所有缓存,建议本地加载可以采取这种方式,忽略缓存,因为缓存空间是有限的,不要影响真正需要缓存的页面

关于我

期待与要求上进的您进一步沟通

微信号:maako127

扫描下方二维码加入我的公众号(二码前端说),定期更新前端相关技术干货