概述
從兩個方面來講:
- js不能直接調用oc的方法
- oc可以通過如下函數調用js代碼
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;

我們從 github上面的demo https://github.com/marcuswestin/WebViewJavascriptBridge
來分析一下這個庫是如何實現js交互的。
詳解
一. js調用native方法
在概述中說過,js是不能直接調用native的method所以,需要借助- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType這個方法。這個方法大家不陌生,每次在重新定向URL的時候,這個方法就會被觸發,通常情況,我們會在這里做一些攔截,用來完成js和本地的間接交互。那么WebViewJavascriptBridge也不另外,也是這么做。

順序分析代碼 :首先看 ExampleApp.html文件中所實現的按鈕事件,兩個按鈕事件分別是bridge.send() 方法和bridge.callHandle()方法 。在 WebViewJavascriptBridge.js 這個文件中可以看到

不了解js的同學(例如我)只需要知道 改變了iframe的src之后,uiwebview 會執行下邊的方法

所以我們就可以關注WebViewJavascriptBridge.m文件中的這個方法中做了些什么事

這里經過判斷之后會走到 223 行代碼,后邊return NO的原因是:我們要執行的是oc的代碼了,所以返回NO來阻斷 js 代碼。這里執行了一段js代碼,點進去-(void)webViewJavascriptFetchQueyCommand方法,發現執行的js語句 WebViewJavascriptBridge._fetchQueue();然后去WebViewJavascriptBridge.js中找到這個方法。

返回一個字典,就是我們在最初要發送消息時存儲起來的字典。現在我們把要傳遞的數據拿出來,里面存儲的東西有:
handlerName:handlerName,
data:data,
callbackId:callbackId
拿到字典繼續向下執行下邊這個方法

總體來看 就是對我們拿出來的數據進行一系列的類型驗證(_log方法是打印數據信息的 可以忽略)回顧前面的代碼,我們就應該知道這里的responseId為空,所以執行86 -- 114行的代碼

這部分是重點,到底他是怎么要調用本地function的,callbackId大家熟悉吧,判斷是否為空,不為空給他指定一個block,這個不說了,block指定,此時不調用(手動調用才會執行),這個剛才說了用來處理native的function處理的result用於把處理后的值返回給js的,接着往下去,看到handler這個方法會從message找到handlerName,這里我們看一下多了一個_messageHandlers字典,從這個字典獲取一個block(WVJBHandler是一個block),直接執行了。那我們看看_messageHandlers是怎么被添加block的:

那又是誰調用了這個方法:(在文件 ExampleAppViewController.m的viewdidload中),這里有方法testObjecCallback

剛才都是倒推的,如果我們反過來,首先肯定是viewdidload初始化,初始化之后會把這個block加入到_messageHandlers的數組中,之后因為js調用動態讀取這個block調用,在調用之前,我們又把一個block付值給回掉處理的responseCallback的block,這個block在handler被調用時而被調用, 略微有點繞。現在我們看一下這個responseCallback怎么賦值的

順着方法往下看 執行到下邊這個方法

對傳進來的數據 @{ @"responseId":callbackId, @"responseData":responseData };處理之后 再執行 js語句 @"WebViewJavascriptBridge._handleMessageFromObjC('%@');"
看看js的方法

這個里面應該很容易看到 代碼進入待66 行 因為傳進來的數據中responseId 顯然不為空 而這里面的responseCallback 方法 和responseCallbacks 數組又是何處來的呢?大家可能已經忘了,回到文首doSend方法(返回去看看),除了包裝一個message 字典存起來,還有把 responseCallback 存在了responseCallBacks[]中,所以等到原生的方法執行完之后再調用這個方法(在最初 button點擊事件里面已經實現了 )實現相互通信。
這里稍微總結下,便於理解
1.首先是在UIWebViewController 里面實例化 一個bridge,通過bridge 注冊一個 handler,然后保存在messageHandlers中
2. 點擊網頁的button的時候,把信息保存起來生成一個message字典三個key(handlerName , data,callbackId(后邊通過這個來找到之前的responseCallback方法)) 並且把 其中的responseCallback保存起來,並且改變iframe.src
3. 這個時候webView執行代理方法,在這里面取出2步存起來的信息,然后給1步的handler中的responseCallback賦值,並且執行 1步注冊的方法。所以結果就是執行oc的回調方法,然后在oc的回調方法里面再去執行,剛剛被賦值的 responseCallback方法(這個方法的響應結果體現在web中),至於這個responseCallback被賦值的過程就是通過第二步的callbackId 找到相應的方法賦過去。
二. native調用js
過程不是直接調用js,跟通過js調用Native的處理方式是一樣的。可以看到,最后調用的就是WebViewJavascriptBridgeBase中的這個方法
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName;

把data、handlerName、callbackId判空並且存在message, 而且把resopnseCallback方法存起來。然后,后邊同上文一樣,最后執行到JS的這個方法

不過這次執行的是 71 - 90 行的代碼,兩個判斷
1 如果有callbakId存在,那么就給實現responseCallback這個方法(並不調用)
2 如果message.handlerName存在,那么就取出messageHandlers中 message.handlerName 對應的方法,這個方法一般是在js代碼中注冊過的

這里最后,如果既有handler又有callback,就會把第一步實現的方法賦值給handler的responseCallback,然后在執行到handler的最后一句 responseCallback(responseData)時候,再執行這個回調。
三. 使用
一般來說,都不會這么復雜的傳輸數據。一般只需要單向的去傳遞數據,不會有很多的callback 來回的調用。至於oc 的初始化和html的初始化,對照github上面的demo進行就可以了 。
大多數情況下都是js在調用native的方法,所以一般都是我們在方法中作如下的工作
[_eBridge registerHandler:@"backToHomeHandle" handler:^(id data, WVJBResponseCallback responseCallback) {
[weakSelf.navigationController popToRootViewControllerAnimated:YES];
}];
這里 @"backToHomeHandle" 就是約定的方法名 block回調中就是當js代碼調用該方法時 我們的原生界面要做出的響應。然后 和 前端的同學 約定好 數據結構 讓他們在適當的時候 調用我們的方法即可。工作中用到了 學習了下 供大家參考。可以領略一下這個庫里面對block回調 以及 js函數式編程的運用。