项目笔记-UIWebView替换为WKWebView

3,802 阅读2分钟

背景

iOS8后推出WKWebView,到现在已经iOS13,UIWebView即将不支持。

对比

UIWebView:

  • 加载速度慢、内存占用多

WKWebView(苹果官方文档):

  • 在性能、稳定性、功能方面有很大提升(高达60fps的滚动刷新率以及内置手势);
  • 支持了更多的HTML5特性、JavaScript库;
  • 将UIWebViewDelegate与UIWebView重构成了14个类与3个协议;

JS交互

关于JS交互这篇很详细

开源方案 WebViewJavascriptBridge

UIWebView:
Native->JS

通过调用执行JS代码的API实现:

NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
JS-> Native

1、在shouldStartLoadWithRequest:代理方法中拦截信息,与前端协商好URL映射到原生方法。该回调在前端href或者iframe变化时触发。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSString *url = request.URL.absoluteString;
    if (url 字符串规则匹配) {
        // 映射方法,执行Native逻辑
        return NO;
    }
    return YES;
}

2、通过JavaScriptCore注入


@protocol JSCallNativeFunc <JSExport>
-(void)dosomething
@end

self Class <JSCallNativeFunc>

//KVC得到WebView的JS上下文
JSContext *context = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//给这个上下文注入JS对象
context[@"nativeObj"] = self;
//给这个上下文注入callNativeFunction函数当做JS对象
context[@"callNativeFunction"] = ^( JSValue * data )
{
    //1 解读JS传过来的JSValue数据
    //2 取出指令参数,确认要发起的native调用的指令
    //3 取出数据参数,拿到JS传过来的数据
    //4 根据指令调用对应的native方法,传递数据
    //5 此时还可以将客户端的数据同步返回
}

-(void)dosomething{}

// js代码
nativeObj.dosomething()
WKWebView:
Native->JS

通过调用执行JS代码的API实现:

[webView evaluateJavaScript:@"document.title"
          completionHandler:^(id _Nullable title, NSError * _Nullable error) {
    }];
JS-> Native

依靠WKScriptMessageHandler协议类和WKUserContentController两个类: WKUserContentController对象负责注册JS方法,设置处理接收JS方法的代理,代理遵守WKScriptMessageHandler,实现捕捉到JS消息的回调方法

WKUserContentController * wkUController = [[WKUserContentController alloc] init];
[wkUController addScriptMessageHandler:self name:@"jsCallNativeFuncName"];
config.userContentController = wkUController;

#pragma mark - WKScriptMessageHandler
// 通过接收JS传出消息的name进行捕捉的回调方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
  // message.name ?= 'jsCallNativeFuncName'
    SEL sel = NSSelectorFromString(message.name);
    if ([_jsCallBack respondsToSelector:sel]) {
        [_jsCallBack performSelector:sel];
    } else {
        NSLog(@"Not Found SEL:%@", message.name);
    }
}

// 前端代码
window.webkit.messageHandlers.jsCallNativeFuncName.postMessage({});

项目应用

针对我们工程现状的WK迁移方案总结

1.项目目录下扫描“UIWebview”,framework中的引用也可以检测出来

grep -r -n "UIWebView" ./

2.API替换

  • 初始化
  • config
  • delegate

对应API:

UIWebView WKWebView
stringByEvaluatingJavaScriptFromString: evaluateJavaScript: completionHandler:
shouldStartLoadWithRequest: decidePolicyForNavigationAction:

3.兼容UIWebView中jsCallNative方法

通过js注入将WKWebView的js调用方式包装到UIWebView中原有的方法中,前端项目无需修改代码实现兼容

NSString *jsString = @"var nativeObj = new Object(); \
nativeObj.jsCallNativeFuncName = function(){ \
    window.webkit.messageHandlers.jsCallNativeFuncName.postMessage({}); \
}";
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jsString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[WKUserContentController addUserScript:wkUScript];

4.关于cookie

/**
我们支持iOS11+,所以可以通过WKHTTPCookieStore以Config给webView注入已存在NSHTTPCookieStorage中的cookie
*/
+ (WKWebViewConfiguration *)wkWebViewConfiguration
{
    WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
    if (@available(iOS 11.0, *)) {
        WKHTTPCookieStore *cookieStore = configuration.websiteDataStore.httpCookieStore;
        NSArray *cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage].cookies;
        for (NSHTTPCookie *cookie in cookies) {
            [cookieStore setCookie:cookie completionHandler:nil];
        }
    }
    return configuration;
}

参考链接