衆所周知WebViewJavaScriptBridge是一個iOS/OSX 在UIWebViews/WebViews中obj-C和javascript發送消息的一個橋接。
WebViewJavaScriptBridge的開源項目在此:這裡。
下面我們來對WebViewJavaScriptBridge的機制來進行分析:(以UIWebView為例)
一、obj-c調用javascript的機制
UIWebView是iOS最常用的SDK之一,它有一個stringByEvaluatingJavaScriptFromString方法可以将javascript嵌入頁面中,通過這個方法我們可以在iOS中與UIWebView中的網頁元素互動。
使用stringByEvaluatingJavaScriptFromString方法,需要等UIWebView中的頁面加載完成之後去調用。
使用例子介紹:
[webView stringByEvaluatingJavaScriptFromString:@"document.location.href"] //擷取目前頁面的url。
[webview stringByEvaluatingJavaScriptFromString:@"document.title"] //擷取頁面title:
[webView stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit();"] //表單送出:
從以上可以看出stringByEvaluatingJavaScriptFromString不僅可以執行js來擷取資訊,還可以通過調用js對webView執行操作。 是以可以通過此函數來實作obj-c到js的互動。
備注: stringByEvaluatingJavaScriptFromString這個方法有個地方需要注意, 算不上bug, 但确實有問題, 需要注意!
如果stringByEvaluatingJavaScriptFromString執行的是帶參數的js函數, 這個參數裡面如果帶有(\r \n ')等等, js那邊收不到這個值, 這些帶\的需要轉義,
二、 javascript調用obj-c
UIWebView對URL的跳轉進行攔截,可以通過自定義協定來捕獲請求,在通過obj-c的stringByEvaluatingJavaScriptFromString來擷取js對于obj-c函數的調用。
UIWebView對于跳轉的攔截方法:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
}
實質上oc與js的通信互動就是發送消息,也即函數調用,隻要在互動的過程正确的指定好對方需要調用的函數和參數就ok
oc-->js stringByEvaluatingJavaScriptFromString,其參數是一NSString 字元串内容是js代碼(這又可以是一個js函數、一句js代碼或他們的組合),當js函數有傳回值或一句js代碼有值傳回可通過stringByEvaluatingJavaScriptFromString的傳回值擷取
js-->oc 利用webView的重定向原理(即重新在js中指定document.location的值,此為一url),隻要在這個url字元串中按自定義的規則指定好所需調用oc中的函數和參數,然後通過OC中的shouldStartLoadWithRequest函數去捕獲處理請求,處理完最後,如果js還想擷取一些傳回參數的話,同樣讓oc去通過stringByEvaluatingJavaScriptFromString調用剛js傳過來的回調js函數就行,順道把參數也一起傳了。
三、WebViewJavaScriptBridge實作機制
webViewJavaScriptBridge 包含三個檔案:
WebViewJavascriptBridge.h
WebViewJavascriptBridge.m
WebViewJavascriptBridge.js.txt
很明顯:WebViewJavascriptBridge.js.txt主要用于銜接UIWebView中的web page,而WebViewJavascriptBridge.h/m則主要用于與ObjC的native code打交道。他們作為一個整體,其實起到了一個“橋梁”的作用,這三個檔案封裝了他們具體的互動處理方式,隻開放出一些對外的涉及到業務處理的API,是以你在需要UIWebView與Native code互動的時候,引入該庫,則無需考慮太多的互動上的問題。整個的Bridge對你來說都是透明的,你感覺程式設計的時候,就像是web程式設計的前端和後端一樣清晰。他們使用互動如圖所示:

WebViewJavaScriptBridge互動圖
下面我們對WebViewJavaScriptBridge的實作做出分析。
3.1 WebViewJavaScriptBridge中的初始化
WebViewJavaScriptBridge的初始化過程如下:
1. 初始化需要使用的資料結果
2. 儲存registerHandler的函數名與block
3. webView 加載完成載人js代碼
具體流程圖如下:
bridge初始化流程圖
3.1 obj-c與js的互動
obj-c與js互動使用的機制是一樣的,是以單項調用的處理是混在一起,是以現在按照流程單獨梳理,最後在以源碼為例來說明。
3.1.1 obj-c調用js過程:
1、obj-c儲存回調的block,使用唯一碼objc_cb_* 辨別
2、obj-c拼裝data(資料),handlerName(調用名),回調辨別等資訊
3、 對資訊進行json拼裝,及js一些字元的轉碼
4、 執行js端的分發消息的函數
5、js執行完成,執行函數塊,調用register的responseCallback,發送responseId和responseData
6、 js拼裝資訊到消息隊列中,URL重定向;obj-c端攔截請求,執行js代碼拉取資料
7、obj-c端根據responseId來取得儲存的block,執行block
大緻流程圖如下:
obj-c調用js流程圖
3.1.2 js調用obj-c的過程:
1、js端儲存data與handlerName的映射
2、js端生成唯一碼callbackId儲存responseCallback映射,并封裝callbackId到發送消息中
3、js端封裝後的消息放入發送消息隊列中, 并執行URL重定向
4、obj-c端攔截請求,判定是自定義協定後,執行js的_fetchQueue()拉取發送的資訊
5、obj-c端重新封裝responcallback 的block,執行時調用_queueMessage進行消息分發
6、從obj-c端的注冊隊列中擷取handler的block,并執行
7、執行後回調block,封裝了responseId和responseData資料,并進行分發
8、資料的特殊字元處理,調用js的函數_handleMessageFromObjC()來傳遞消息
9、js擷取分發的responseId,擷取儲存在隊列中的function
大緻的流程圖如下:
js調用obj-c流程圖
3.2 源碼解讀
3.2.1 obj-c端源碼
因js調用涉及到自定義協定的重定向,是以定義了協定及host
#define kCustomProtocolScheme @"wvjbscheme"
#define kQueueHasMessage@"__WVJB_QUEUE_MESSAGE__"
WebViewJavaScriptBridge的初始化因為涉及到OSX端WebView與iOS端UIWebView,故根據不同的系統來做變量的統一定義,如下:
OS X與iOS變量重定義
定義block,WVJBResponseCallback定義了回調block,js傳回資料後回調。WVJBHandler定義了register的block,用作不同情況下的處理。
typedef void(^WVJBResponseCallback)(id responseData);
typedef void(^WVJBHandler)(id data,WVJBResponseCallback responseCallback);
obj-c外部接口,用于native調用使用。其中register與callhandler函數封裝資料後調用send處理。
obj-c與js互動函數
send函數發送消息到js端的源碼:
send封裝到調用js代碼
參數data為傳遞給js函數的參數,可為NSString、NSDictionary等,responseCallback則為obj-C的回調,此回調函數執行流程簡述為“js注冊函數執行完畢後,會傳回帶有responseId的消息,最後在obj-c會取出(回調存儲在responCallbacks字典中),并執行”,handlerName則為js定義的函數名稱。
源碼中在_queueMessage方法進行邏輯判斷:若在H5頁面或者js資源并未加載完畢時,在obj-C的webview中就調用了js函數,則會把相關的操作(存儲為Message格式)存儲在startupMessageQueue,等待相關資源加載完畢(即在webview的webViewDidFinishLoad生命周期函數中執行存儲在startupMessageQueue的指令數組,執行完畢并清除改隊列)再調用js的函數;否則若startupMessageQueue隊列為空,則直接執行暴露在js端的webViewJavascriptBridge.handleMessageFromObjC函數,擷取被調用的函數名和傳人參數,以及在objC的sendData:responseCallback:handlerName中設定的回調函數id—callbackId,最終執行js注冊函數,并最終想obj-c端發送“doSend({ responseId:callbackResponseId, responseData:responseData })”格式的消息,待objC接收到消息,解析responseId,執行回調函數。
webView載入H5的delegate函數:
finishLoad函數
H5 頁面載入完成後,載入js資源,在對開始消息隊列中的資料進行處理,并置nil。 在把delegate抛出。
url攔截
有重定向請求時,首先判斷scheme及host是自定的,使用_flushMessageQueue函數處理。
obj-c端對js端處理
這段代碼通過回傳消息的responseId來判斷是回調還是call,來進行不同的解封調用。
3.2.2 js端源碼
js端定義的WebViewJavascriptBridge對象:
js端WebViewJavascriptBridge類
js端的互動函數,邏輯處理,一個registerHandler和一個callHandler主要封裝。_dosend函數進行消息封裝放入發送消息隊列,在産生一個src(url scheme),供obj-c端shouldStartLoadWithRequest捕捉
消息互動函數
obj-c端通過_fetchQueue()擷取發送的消息
js拉取發送的消息
js中處理來自objc的消息,判斷同obj-c擷取到js端的消息
_dispatchMessageFromObjC