iOS WKWebView 与 JS 交互

应对苹果公司的号召,2020年还是要把之前老项目的UIWebView都替换成WKWebView。
单纯换View倒也不难,除了代理方法有点区别之外,加载网页的使用方式都是类似的。
但现在越来越多应用都采用混合开发的模式,所以就需要Native与JS之间进行良好的通信。
之前UIWebView与JS通信是采用JavaScriptCore来实现的
通过给JSContext动态注入OC对象来实现JS调用Native
由于以后苹果不允许使用UIWebView我这里也就不多讲述其实现了
主要还是说一说WKWebView是如何与JS之间交互的!

开讲之前

今天我们从两个方面来讲述OC与JS交互:
1、通过原生的WKWebViewConfiguration
2、通过使用WebViewJavascriptBridge

通过Native调用JS的方式,没有任何争议,几乎都是用WebView提供的

[webView evaluateJavaScript:@"" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        
}];

这里我们重点讨论JS如何调用Native

WKWebViewConfiguration、WKUserContentController

image.png

WKWebViewConfiguration对象有一个很关键的属性参数userContentController
我们可以把它当做内容交互控制器,可以自己注入JS代码及JS调用原生方法注册

- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

addScript需要与remove成对出现,在dealloc时需要注意调用

- (void)removeScriptMessageHandlerForName:(NSString *)name;

在JS里我们可以通过

window.webkit.messageHandlers.<注册的方法名>.postMessage(<需要传递的参数>);

的方式来调用我们在Native端注册的方法
同时Native会通过WKScriptMessageHandler代理方法

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

接收JS传递过来的参数,参数内容都封装在WKScriptMessage对象中
其中,name为注册的方法名,body为传递的参数对象
由于只能接收一个参数,所以JS通常采取Object转JSON字符串的形式传递多参数,Native这边用NSDictionary接收

WebViewJavascriptBridge

这是github上非常火的一个桥接库https://github.com/marcuswestin/WebViewJavascriptBridge有12.9k的Star
看了看他的源码,整个处理的非常优雅
主要是利用iframe设置src的方式通知Native完成通信过程
所以从UIWebView到WKWebView的改动几乎很小,并且还支持相互的通信之间的回调,可以说是非常全面的Bridge了
我们在这里简单的分析一下这个库是如何实现通信的

简易流程图

开始之前我们先看一下这个库的文件结构
image.png

整个库总共也就八个文件,算是十分精简也是十分清晰了
核心代码在WebViewJavascriptBridge_JSWebViewJavascriptBridgeBase
前者负责实现注入JS的通信解析对象
后者负责实现Native解析JS信息的对象
其余两个类文件是用来扩展到UIWebView和WKWebView的,主要是实现对应的协议方法,用作拦截URL

JS调用Native

打开WebView页面时,默认通过iframe设置

WVJBIframe.src = 'https://__bridge_loaded__';

客户端在拦截到初始化的指令时会通过这两步进行预设好的JS注入

if ([_base isBridgeLoadedURL:url]) {
    [_base injectJavascriptFile];
}
......
- (void)injectJavascriptFile {
    NSString *js = WebViewJavascriptBridge_js();
    [self _evaluateJavascript:js];
}

初始化工作做好之后,按照流程就是Native端的注册方法

[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
     NSLog(@"testObjcCallback called: %@", data);
     responseCallback(@"Response from testObjcCallback");
}];

等待JS端的调用
JS通过bridge对象的callHandler方法

var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
     callbackButton.innerHTML = 'Fire testObjcCallback'
     callbackButton.onclick = function(e) {
     bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
          log('JS got response', response)
     })
}
......

function callHandler(handlerName, data, responseCallback) {
     if (arguments.length == 2 && typeof data == 'function') {
            responseCallback = data;
            data = null;
     }
     _doSend({ handlerName:handlerName, data:data }, responseCallback);
}  
function _doSend(message, responseCallback) {
     if (responseCallback) {
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message['callbackId'] = callbackId;
     }
     sendMessageQueue.push(message);
     messagingIframe.src = 'https://__wvjb_queue_message__/';
}

拦截URL变化主要是通过WebView的delegate获取

/// UIWebView 通过
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
/// WKWebView 通过
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

设置了src为https://__wvjb_queue_message__/,Native拦截之后通过获取sendMessageQueue中的message完成方法名的提取和参数的提取

[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
        if (error != nil) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
        }
        [_base flushMessageQueue:result];
}];

通过messageJSONString中的信息匹配到之前注册的方法名和block,调用执行。
如果Native注册的方法中有返回responseCallBack信息,则将返回的信息参数与JS提供的callBackId组成新的对象,通知JS解析,JS通过callBackId找到对应方法并传递参数执行

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];

以上就是完整的JS调用Native流程

Native调用JS

反过来,Native调用JS,其实双方注册方法和调用方法都是类似的,整个流程反一下就可以了,具体我这里就不细说了,只要前面的理解了,后面就很容易想明白了,可以参考源码Demo。

总结一下

1、JS通知Native信息变化通过

iframe.src='https://__bridge_loaded__/'                  // 初始化
iframe.src='https://__wvjb_queue_message__/'            // 发送消息

2、Native通知JS传递参数通过

[webView evaluateJavaScript:@"" completionHandler:^(id _Nullable result, NSError * _Nullable error) {}];

整个交互过程依赖双方存储注册方法信息,并通过方法名和参数来完成跨界调用

这个第三方库考验开发者要有一些基础的前端开发经验,否则对于里面近半数的JS源码可能不太理解,重点是设置iframe.src

以上就是本次我想分享的关于WKWebView与JS交互的内容
ps:内容建议结合源码Demo一起食用,更容易理清思路
希望对你有帮助

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容