概述
? 從兩個方面來講:
? js不能直接調(diào)用oc的方法
?oc可以通過
?- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ ? ? ?__nullable)(__nullable id, NSError * __nullable error))completionHandler;
? 這個方法調(diào)用js 的代碼
我們從 github上面的demo https://github.com/marcuswestin/WebViewJavascriptBridge
來分析一下這個庫是如何實現(xiàn)js交互的绪钥。
詳解
一. ?js調(diào)用native方法
在概述中說過灿里,js是不能直接調(diào)用native的method所以,需要借助- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType程腹,這個方法大家不陌生匣吊,每次在重新定向URL的時候,這個方法就會被觸發(fā),通常情況色鸳,我們會在這里做一些攔截完成js和本地的間接交互什么的社痛。那么WebViewJavascriptBridge也不另外,也是這么做命雀。
順序分析代碼 : ? 首先看 ExampleApp.html ?文件中所實現(xiàn)的按鈕事件 兩個按鈕時間分別是bridge.send() 方法 ? 和 ? bridge.callHandle()方法 ? ? ? 在 WebViewJavascriptBridge.js 這個文件中可以看到
最終都是調(diào)用了doSend 方法 ? 分析下這個方法蒜哀,變量callbackId是個字符串,responseCallBacks[] 一看就知道是個字典 吏砂,這個字典把回調(diào)的方法responseCallback給保存起來撵儿,這Key(也就是callbackId)是唯一的,通過計數(shù)和時間應(yīng)該知道這個字符串應(yīng)該是唯一的狐血,message也是一個字典淀歇,這是給message添加了一個新的key-value。干嘛呢氛雪?我也不知道,我們來看看sendMessageQueue是什么耸成,大家一看push就知道應(yīng)該是個數(shù)組报亩。他吧一個字典放到一個消息隊列中(數(shù)組隊列),讓后產(chǎn)生一個src(url scheme)井氢。
不了解js的同學(xué)(例如我) 只需要知道 改變了iframe的src ?之后 ?uiwebview 會執(zhí)行下邊的方法
所以我們就可以關(guān)注WebViewJavascriptBridge.m文件中的這個方法中做了些什么事
這里經(jīng)過判斷之后會走到 223 行代碼 ?后邊return NO; 的原因想必大家也清楚了 ?是因為我們要執(zhí)行的是 oc的代碼了 ?所以會阻斷 js 代碼弦追。 ?這里執(zhí)行了一段js代碼 ?點進去
-(void)webViewJavascriptFetchQueyCommand 方法
執(zhí)行的js語句 WebViewJavascriptBridge._fetchQueue();
然后去WebViewJavascriptBridge.js中找到這個方法
返回一個字典 ?就是我們在最初 要發(fā)送消息時存儲起來的字典 現(xiàn)在我們把要傳遞的數(shù)據(jù)拿出來 ? ? 那里面有哪些東西,我么來看看
handlerName:handlerName,
data:data,
callbackId:callbackId
拿到字典繼續(xù)向下執(zhí)行 ? 下邊這個方法
總體來看 就是對我們拿出來的數(shù)據(jù)進行一系列的類型驗證 (_log方法是打印數(shù)據(jù)信息的 可以忽略) ? 回顧前面的代碼 我們就應(yīng)該知道這里的responseId為空 ?所以執(zhí)行 ?86 -- 114 ?行的代碼
這部分是重點花竞,到底他是怎么要調(diào)用本地function的劲件,callbackId大家熟悉吧,判斷是否為空约急,不為空給他指定一個block零远,這個不說了,block指定厌蔽,此時不調(diào)用(手動調(diào)用才會執(zhí)行)牵辣,這個剛才說了用來處理native的function處理的result用于把處理后的值返回給js的,接著往下去奴饮,看到handler這個方法會從message找到handlerName纬向,這里我們看一下多了一個_messageHandlers字典,從這個字典獲取一個block(WVJBHandler是一個block)戴卜,直接執(zhí)行了逾条。那我們看看_messageHandlers是怎么被添加block的:
那又是誰調(diào)用了這個方法:(在文件 ExampleAppViewController.m的viewdidload中),這里有方法testObjecCallback
剛才都是倒推的投剥,如果我們反過來师脂,首先肯定是viewdidload初始化,初始化之后會把這個block加入到_messageHandlers的數(shù)組中,之后因為js調(diào)用動態(tài)讀取這個block調(diào)用危彩,在調(diào)用之前攒磨,我們又把一個block付值給回掉處理的responseCallback的block,這個block在handler被調(diào)用時而被調(diào)用汤徽,有點繞娩缰。 略微有點繞。 ? 現(xiàn)在我們看一下 這個responseCallback ?怎么賦值的 ?
順著方法往下看 ?執(zhí)行到下邊這個方法
對傳進來的數(shù)據(jù) ? @{ @"responseId":callbackId, @"responseData":responseData };處理之后 再執(zhí)行 js語句 ? @"WebViewJavascriptBridge._handleMessageFromObjC('%@');"
看看js的方法 ?
這個里面應(yīng)該很容易看到 ?代碼進入待66 行 ? 因為傳進來的數(shù)據(jù)中responseId 顯然不為空 ?而這里面的responseCallback 方法 ?和responseCallbacks 數(shù)組又是何處來的呢谒府? ?大家可能已經(jīng)忘了 拼坎,回到文首 ?doSend 方法(返回去看看) ?除了包裝一個message 字典存起來 還有 ?把 responseCallback 存在了 responseCallBacks[] ? 中, 所以等到原生的方法執(zhí)行完之后 ?再調(diào)用這個方法 (在最初 button點擊事件里面已經(jīng)實現(xiàn)了 ) 實現(xiàn)相互通信完疫。
這里稍微總結(jié)下 便于理解
1.首先是在UIWebViewController 里面實例化 一個bridge ? ? 通過bridge 注冊一個 handler ?然后保存在messageHandlers中 ?
2. 點擊網(wǎng)頁的button的時候 ?把信息保存起來生成一個message 字典 三個key(handlerName 泰鸡, data,callbackId(后邊通過這個來找到之前的responseCallback方法)) ? 并且把 其中的responseCallback 保存起來 ? ?并且改變iframe.src ?
3. 這個時候 webView 執(zhí)行代理方法 ? ?在這里面取出 2步 存起來的信息 ?然后給1步的handler中的responseCallback賦值 ? 并且執(zhí)行 1步注冊的方法 ? ?
? 所以結(jié)果就是 執(zhí)行oc的回調(diào)方法 ?然后在oc的回調(diào)方法里面再去執(zhí)行 ?剛剛被賦值的 responseCallback方法(這個方法的響應(yīng)結(jié)果體現(xiàn)在web中) ? 至于這個responseCallback被賦值的過程就是通過第二步的callbackId 找到相應(yīng)的方法賦過去壳鹤。
二. native調(diào)用js
過程不是直接調(diào)用js盛龄,也是通過js調(diào)用Native過程一樣的處理方式》际模可以看到 最后調(diào)用的就是WebViewJavascriptBridgeBase ?中的這個方法 ? - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName; ?
把 data ?handlerName ?callbackId ?判空并且存在message 并且把resopnseCallback 方法存起來 ? 然后 后邊同上文一樣 ? 最后執(zhí)行到 JS的這個方法
不過這次執(zhí)行的是 71 - 90 行的代碼 ? ? 兩個判斷 ?
1 ?如果有callbakId存在 ?那么就給實現(xiàn) responseCallback ?這個方法(并不調(diào)用) ?
2 ?如果message.handlerName存在 ? 那么就取出messageHandlers中 message.handlerName 對應(yīng)的方法 ?這個方法一般是在js代碼中注冊過的
這里最后 如果既有handler 又有callback ? ?就會把第一步實現(xiàn)的方法賦值給handler的 responseCallback ? ? ?然后在執(zhí)行到handler的最后一句 ?responseCallback(responseData) ?時候 再執(zhí)行 ?執(zhí)行這個回調(diào)余舶。
三. ?使用 ?
一般來說 都不會這么復(fù)雜的傳輸數(shù)據(jù) ?一般只需要單向的去傳遞數(shù)據(jù) ?不會有很多的callback 來回的調(diào)用。至于oc 的初始化 和 html的初始化 對照github上面的demo進行就可以了 锹淌。 ? ? ?
大多數(shù)情況下 都是 js在調(diào)用原生的 方法 ? 所以 一般都是我們在 我們的方法中作如下的工作
[_eBridge registerHandler:@"backToHomeHandle" handler:^(id data, ? ? ? ? ? ? ? ?WVJBResponseCallback responseCallback) {
? ? ? ? [weakSelf.navigationController popToRootViewControllerAnimated:YES];
}];
這里 ?@"backToHomeHandle" 就是約定的方法名 ?block回調(diào)中就是當js代碼調(diào)用該方法時 我們的原生界面要做出的響應(yīng)匿值。然后 和 前端的同學(xué) 約定好 數(shù)據(jù)結(jié)構(gòu) ? 讓他們在適當?shù)臅r候 調(diào)用我們的方法即可。工作中用到了 學(xué)習(xí)了下 供大家參考赂摆⌒荆可以領(lǐng)略一下這個庫里面對block回調(diào) 以及 js函數(shù)式編程的運用。