iOS WKWebview與JS交互的兩種方式對(duì)比

在APP加載的網(wǎng)頁中嗽仪,我們需要用到原生與JS 交互锡垄。本人做過的項(xiàng)目里有兩種實(shí)現(xiàn)方式:A破镰、用的是系統(tǒng)原生的方式實(shí)現(xiàn)。B默辨、用的是第三方寫的一個(gè)類庫WebViewJavascriptBridge. 先就兩種方式進(jìn)行對(duì)比總結(jié)德频。

A、系統(tǒng)原生的方式實(shí)現(xiàn)
// 1. webview調(diào)用JS函數(shù), JS代碼可根據(jù)需要拼接好缩幸。
NSString *JSFunc = xxx;
[self.webView evaluateJavaScript:JSFunc completionHandler:^(id _Nullable result, NSError * _Nullable error) {
   NSLog(@"evaluateJavaScript:\n result = %@ error = %@",result, error);
}];

// 2. 網(wǎng)頁JS調(diào)原生: 
//   1> 需要先設(shè)置Webview. config 的WKUserContentController
//   2> 注冊(cè)方法名 [userCC addScriptMessageHandler:self name:];
//   3> 遵守協(xié)議<WKScriptMessageHandler>壹置,實(shí)現(xiàn)其方法.
//   4> 在控制器銷毀時(shí),需要移除方法名注冊(cè)
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *userCC = [WKUserContentController new];
config.userContentController = userCC;
//JS調(diào)用OC 添加處理腳本
[userCC addScriptMessageHandler:self name:JSMessageName_Register];
WKWebView *webView = [[WKWebView alloc] initWithFrame:webFrame configuration:config];

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:JSMessageName_Register]) {
        RegisterViewController *controller = [RegisterViewController new];
        [self.navigationController pushViewController:controller animated:YES];
    }
}
- (void)dealloc
{
    WKUserContentController *userCC = self.webView.configuration.userContentController;
    [userCC removeScriptMessageHandlerForName:JSMessageName_Register]; // 不移除表谊,怕是會(huì)造成webview無法釋放
}
這種方式下H5端該怎么做钞护?

使用原生的交互方式的話,安卓端的交互不一致爆办,需要H5端自己判斷难咕,比如:

//android終端或者uc瀏覽器
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1;
//ios終端
var isiOS = !!u.match(/(i[^;]+;( U;)? CPU.+Mac OS X)/);
if(isiOS){
// 假設(shè)方法名為: JSMessageName;postMessage里面可以傳參數(shù)
    window.webkit.messageHandlers.JSMessageName.postMessage(['13300001111', 'Go Climbing This Weekend !!!'])
}else if(isAndroid){
    .....
}
B、WebViewJavascriptBridge的方式

一余佃、使用方式

// 1.JS調(diào)用原生:假設(shè)方法名是getUserInfo
[_bridge registerHandler:@"getUserInfo" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[@"custName"] = [UserModel shareUserModel].custName;
        dict[@"idCard"] = [UserModel shareUserModel].certifino;
        responseCallback([JSONKit jsonStringWithObject:dict]);
    }];

// 2. webview調(diào)用JS: 假設(shè)JS的方法名是redirect
[_bridge callHandler:@"redirect" data:@"test" responseCallback:^(id responseData) {
        NSLog(@"%@", responseData);
 }];

通過方法名與對(duì)應(yīng)的操作放在一起的方式更有利于維護(hù)暮刃;除此之外,這個(gè)提供了一份JS模板文件爆土,安卓端也可以是同樣的原理實(shí)現(xiàn)沾歪。
二、實(shí)現(xiàn)原理
webview調(diào)用JS的原理
1.使用方法-(void)callHandler:data:responseCallback: 傳遞方法名雾消、參數(shù)、block挫望。
-> 2. 內(nèi)部用字典將 block保存立润,然后將方法名、參數(shù)媳板、保存block對(duì)應(yīng)的key這些信息按照J(rèn)S模板拼裝好JS代碼桑腮。
-> 3. 回調(diào)到主線程中使用[self.webView evaluateJavaScript:completionHandler:];方法調(diào)用JS。
-> 4. H5端收到這個(gè)調(diào)用之后蛉幸,調(diào)用iframe實(shí)現(xiàn)對(duì)約定好的地址跳轉(zhuǎn)CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE

//iframe可以理解為webview中的窗口破讨,當(dāng)我們改變iframe的src屬性的時(shí)候,相當(dāng)于我們?yōu)g覽器實(shí)現(xiàn)了鏈接的跳轉(zhuǎn)奕纫。比如從www.baidu.com跳轉(zhuǎn)到www.google.com提陶。下面這段代碼的目的就是實(shí)現(xiàn)一個(gè)到https://__bridge_loaded__的跳轉(zhuǎn)
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(WVJBIframe);

-> 5. 在跳轉(zhuǎn)時(shí),我們的webview會(huì)收到代理方法回調(diào), 在這里做出對(duì)URL判斷是否是我們約定好的協(xié)議頭匹层,如果是約定好的JS與原生交互的就進(jìn)入交互處理隙笆。

- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
 NSURL *url = navigationAction.request.URL;
  if ([_base isCorrectProcotocolScheme:url]) {// 是否是約定好的協(xié)議頭
        if ([_base isBridgeLoadedURL:url]) {// 是否是將橋接JS代碼加載到網(wǎng)頁的對(duì)應(yīng)的url
            [_base injectJavascriptFile];// js代碼注入
        } else if ([_base isQueueMessageURL:url]) {// 是否是JS與原生交互的url
            NSLog(@"JS與原生交互原理的地方");
            [self WKFlushMessageQueue];
        } else {// 是約定好的協(xié)議頭,但是未知的url
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);// 因?yàn)檫@里是處理交互不需要加載
        return;
    }
 ......
}

-> 6. 主動(dòng)調(diào)用JS代碼升筏,從而獲取響應(yīng)的數(shù)據(jù)result.

NSString *js = @"WebViewJavascriptBridge._fetchQueue();";
[_webView evaluateJavaScript:js completionHandler:^(NSString* result, NSError* error) {
     [_base flushMessageQueue:result];
}];

-> 7.對(duì)響應(yīng)的數(shù)據(jù)result進(jìn)行處理, 從中得到我們保存在字典中相應(yīng)的block調(diào)用( _responseCallbacks[responseId])撑柔。

id messages = [self _deserializeMessageJSON: result];
NSString* responseId = message[@"responseId"];
if (responseId) {
     WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
     responseCallback(message[@"responseData"]);
     [self.responseCallbacks removeObjectForKey:responseId];
 }

JS調(diào)用原生的原理

  1. JS調(diào)用原生時(shí),首先我們會(huì)在webview的代理方法里面收到您访,還是原來的方法, 判斷是否是交互的操作跟上面的一樣, 下面是縮減后的代碼铅忿。
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
  if ([_base isQueueMessageURL:url]) {// 是否是JS與原生交互的url
      NSLog(@"JS與原生交互原理的地方");
      [self WKFlushMessageQueue];
      decisionHandler(WKNavigationActionPolicyCancel);
  }
}

-> 2. 主動(dòng)調(diào)用JS代碼,從而獲取響應(yīng)的數(shù)據(jù)result.

NSString *js = @"WebViewJavascriptBridge._fetchQueue();";
[_webView evaluateJavaScript:js completionHandler:^(NSString* result, NSError* error) {
     [_base flushMessageQueue:result];
}];

-> 3. 對(duì)這個(gè)result進(jìn)行處理灵汪。

- (void)flushMessageQueue:(NSString *)messageQueueString{
    id messages = [self _deserializeMessageJSON:messageQueueString];
    WVJBResponseCallback responseCallback = NULL;
    NSString* callbackId = message[@"callbackId"];
    if (callbackId) {
// 調(diào)用原生方法時(shí)的參數(shù)中有 callbackId檀训,則需要我們?cè)谑褂脮r(shí)的那個(gè)block中手動(dòng)調(diào)用這個(gè)responseCallback。
         responseCallback = ^(id responseData) {
               if (responseData == nil) {   responseData = [NSNull null];   }    
               WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
               [self _queueMessage:msg];// 里面會(huì)再次使weview調(diào)用js給網(wǎng)頁傳遞數(shù)據(jù)‘ responseData’
          };
    } else {
         responseCallback = ^(id ignoreResponseData) {
                 // Do nothing
          };
   }
//self.messageHandlers的來源: - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler享言。
    WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
    handler(message[@"data"], responseCallback);
}

附:第三種方式:
OC與JS交互之JavaScriptCore

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肢扯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子担锤,更是在濱河造成了極大的恐慌蔚晨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铭腕,居然都是意外死亡银择,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門累舷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浩考,“玉大人,你說我怎么就攤上這事被盈∥瞿酰” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵只怎,是天一觀的道長袜瞬。 經(jīng)常有香客問我,道長身堡,這世上最難降的妖魔是什么邓尤? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮贴谎,結(jié)果婚禮上汞扎,老公的妹妹穿的比我還像新娘。我一直安慰自己擅这,他們只是感情好澈魄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仲翎,像睡著了一般一忱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谭确,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天帘营,我揣著相機(jī)與錄音,去河邊找鬼逐哈。 笑死芬迄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昂秃。 我是一名探鬼主播禀梳,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肠骆!你這毒婦竟也來了算途?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蚀腿,失蹤者是張志新(化名)和其女友劉穎嘴瓤,沒想到半個(gè)月后扫外,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廓脆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年筛谚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片停忿。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡驾讲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出席赂,到底是詐尸還是另有隱情吮铭,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阅束,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宋渔。 院中可真熱鬧川陆,春花似錦、人聲如沸胧奔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽龙填。三九已至胳泉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岩遗,已是汗流浹背扇商。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宿礁,地道東北人案铺。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像梆靖,于是被迫代替她去往敵國和親控汉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355