iOS WKWebView适配(基础篇)

一、初始化

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种请求错误:

  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 
@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加载本地方式打开页面。

对于资源包要求

  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 服务器做比对后客户端比较后,决策是否缓存

iOS WKWebView适配(基础篇)_第1张图片

Last-Modified / If-Modified-Since:原理类似上面,只不过是用时间的新旧来决策缓存

iOS WKWebView适配(基础篇)_第2张图片

2.强缓存 (本地缓存,200 from memory cache/from disk cache)

Expires(1.0产物,基本可以忽略) 第一次请求返回一个head,值是一个时间点,下次如果再请求相同资源,判断时间是否过期,如果未过期则命中缓存

Cache-Control,主要指定max-age={xxx sencods}

iOS WKWebView适配(基础篇)_第3张图片

2.NSURLRequestReloadIgnoringLocalCacheData

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

关于我

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

微信号:maako127

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

iOS WKWebView适配(基础篇)_第4张图片

你可能感兴趣的:(前端)