1. 程式人生 > >使用JavaScriptCore在JS和OC間通訊

使用JavaScriptCore在JS和OC間通訊

1.jpg

iOS 開發中,我們時不時的需要載入一些 Web 頁面,一些需求使用 Web 頁面來實現可以更可控,如上線後也可以釋出更新,修改 UI 佈局,或者修復 bug,這些 Web 頁面的作用不止是展示,很大一部分是需要和原生程式碼實現的 UI 和業務邏輯發生互動的,那麼不可避免的,就需要用一些方法來實現 Web 頁面(主要是 JavaScript)和原生程式碼之間的通訊,在 JavaScriptCore 出現之前,很多專案都在用 WebViewJavascriptBridge 作為 Web 頁面和原生程式碼之間的一個橋樑(bridge),來傳輸一些資料和方法的呼叫,如 Facebook Messenger

Facebook Paper 等。

WebViewJavascriptBridge 原理簡述

WebViewJavascriptBridge 的原理是通過自定義 scheme,在載入一個特定標識的URL( wvjbscheme://__BRIDGE_LOADED__)時在 UIWebView 的代理方法 webView:shouldStartLoadWithRequest:navigationType: 中攔截 URL 並通過 UIWebView 的 stringByEvaluatingJavaScriptFromString: 方法執行一段 JS,這個 JS 檔案中聲明瞭一些變數和方法,在通訊中作為一個橋樑,那麼怎麼通訊呢?

JS 中呼叫 OC 的方法

在 OC 中,例項化一個 WebViewJavascriptBridge 並呼叫 registerHandler:handler: 註冊並監聽一下事件,第一個引數是一個字串,用來標識一個特定的事件,handler 是一個 block,方法內部將標識作為 keyhandler 作為值儲存。

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

當 JS 中需要呼叫 OC 的方法時,組裝一個類似結構的資料,一個字串作為標識,將需要傳輸的資料作為值並儲存在一個全域性陣列中

var sendMessageQueue = [];
function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message['callbackId'] = callbackId;
        }
        // 主要就是這一行,將 message 儲存到全域性陣列,供待會兒查詢
        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

並觸發一個特定的 URL(wvjbscheme://__WVJB_QUEUE_MESSAGE__),UIWebView 則在 webView:shouldStartLoadWithRequest:navigationType: 中攔截這個 URL,並執行一段 JS(WebViewJavascriptBridge._fetchQueue();

function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}

查詢 JS 中全域性陣列中的值,並轉成 JSON 字串返回,OC 中拿到 JSON 字串,並解析,得到一個數組,遍歷陣列,根據陣列中每個物件的 handlerName 查詢 OC 中是否有註冊這個事件,如果有註冊,則根據 handlerName 取出儲存在字典中的 block,並執行這個 block,block 可以接收一個 id 型別的引數,將 JS 全域性陣列中根據 handlerName 取出來的資料作為引數傳入 block。這樣就實現了從 JS 到 OC 中的資料傳輸。

OC 中呼叫 JS 的方法

OC 中呼叫 JS 的方法相對簡單,因為 UIWebView 可以主動執行 JS,JS 中可以將需要監聽的事件註冊,同樣是字串作為標識,一個函式作為值,儲存到一個全域性物件中,在 OC 中主動執行特定的 JS 方法時,將資料封裝成 JSON 字串,傳入識別符號和資料,並遍歷 JS 中儲存 handler 的全域性物件,看有沒有註冊相應的事件,如果有,根據 事件的名字得到一個函式並執行。實現了 OC 呼叫 JS 中的方法並向 JS 中傳輸資料。

JavaScriptCore 時代的通訊

iOS 7 開始,蘋果提供了一個叫作 JavaScriptCore 的框架,使用 JavaScriptCore 框架可以實現 OC 和 JS 的互相呼叫,而不需要依賴「橋」來實現,怎麼通訊呢?

JavaScriptCore 中 OC 呼叫 JS 方法

在 JS 中定義一個方法

  function alertFunc() {
    window.alert("這是一個JS中的彈框!")
  }

在 webViewDidFinishLoad: 代理方法中,獲取到 JSContext 物件

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    [context setExceptionHandler:^(JSContext *ctx, JSValue *expectValue) {
        NSLog(@"%@", expectValue);
    }];
    
    self.context = context;
}

在一個 button 的點選事件中可以根據 JS 定義的方法的名字獲得一個 JSValue 型別物件,這個物件就是在 JS 中定義的方法,JSValue 物件通過呼叫 callWithArguments: 方法,執行這個 JS 方法。

- (IBAction)buttonClick:(UIButton *)sender {
    if (!self.context) {
        return;
    }
    
    JSValue *funcValue = self.context[@"alertFunc"];
    [funcValue callWithArguments:nil];
}

點選按鈕時,效果如下。

實現了 OC 中呼叫 JS 的方法。

JS 呼叫 OC 中的方法

在 OC 中,通過給 JSContext 的一個 key 賦值,值為一個 block,key 是 JS 中呼叫的方法的名字,程式碼如下:

    self.context[@"ocAlert"] = ^{
        // block 非同步執行,如果涉及到 UI 的操作需要回到主執行緒操作
        dispatch_async(dispatch_get_main_queue(), ^{
            __strong typeof(weakSelf) strongSelf = weakSelf;
            UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"" message:@"這是OC中的彈框!" preferredStyle:UIAlertControllerStyleAlert];
            [alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                [alert dismissViewControllerAnimated:YES completion:^{
                    
                }];
            }]];
            [strongSelf.navigationController presentViewController:alert animated:YES completion:nil];
        });
    };

在 Web 頁面中建立一個 button 並設定 button 的 onClick 事件呼叫 ocAlert 方法

點選這裡

點選 Web 頁面上的 button 按鈕,效果如下

實現了 JS 呼叫 OC 中的方法。

是不是方便了很多?

寫在後面

嗯 ,一篇文章應該有個寫在後面的。

以上當然只是 JavaScriptCore 框架的一個很小的應用,使用 JavaSciptCore 框架結合 Objective-C 的動態性可以做很多事,比如著名的熱修復框架 JSPatch 就是這兩者的結合。這裡只是演示了 JS 和 OC 之間的方法呼叫,並沒有傳輸資料,JavaScriptCore 框架是很容易的實現兩者之間的資料傳輸的。具體做法可以參考參考資料。

蘋果新增的這些新特性可以給開發帶來很多便利,就是不知道有坑沒有,嗯,且爬且珍惜吧。

參考資料