WebViewJavascriptBridge源碼探究--看OC和JS交互過程

BusterMcCra 9年前發布 | 30K 次閱讀 JavaScript開發 JavaScript

今天把實現OC代碼和JS代碼交互的第三方庫 WebViewJavascriptBridge源碼看了下,oc調用js方法我們是知道的,系統提供了 stringByEvaluatingJavaScriptFromString函數

。現在主要是了解js是如何調用oc方法的,分享下探究過程。

源碼不多,就一個頭文件WebViewJavascriptBridge.h和實現文件WebViewJavascriptBridge.m, 和一個js文件,實現在js那邊可以調用oc方法,也可以在oc里面調用js方法。

先上圖,實現簡單的oc和js互相調用的demo, 另外附加一個模擬項目中用到的oc和js互相調用場景:

一、然后說說js調用oc方法的原理,它們是如何實現的?庫文件三個

我們跟蹤下oc控制器加載UIWebView的過程和js調用oc方法過程

1、程序啟動,在自定義控制器里,創建一個 WebViewJavascriptBridge對象時,會加載 WebViewJavascriptBridge.js.txt文件,里面是初始js代碼

在這個js里面,創建了一個 WebViewJavascriptBridge腳本對象,另外創建一個隱藏的iframe標簽:每次js調用oc方法,都是修改iframe標簽的src來觸發UIWebView的代理監聽方法


;(function() {
    if (window.WebViewJavascriptBridge) { return }
    var messagingIframe
    var sendMessageQueue = []
    var receiveMessageQueue = []
    var messageHandlers = {}

    var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
    var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

    var responseCallbacks = {}
    var uniqueId = 1

    function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe')
        messagingIframe.style.display = 'none'
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
        doc.documentElement.appendChild(messagingIframe)
    }

    function init(messageHandler) {
        if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') }
        WebViewJavascriptBridge._messageHandler = messageHandler
        var receivedMessages = receiveMessageQueue
        receiveMessageQueue = null
        for (var i=0; i<receivedMessages.length; i++) {
            _dispatchMessageFromObjC(receivedMessages[i])
        }
    }

    function send(data, responseCallback) {
        _doSend({ data:data }, responseCallback)
    }

    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler
    }

    function callHandler(handlerName, data, responseCallback) {
        _doSend({ handlerName:handlerName, data:data }, responseCallback)
    }

    function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
            responseCallbacks[callbackId] = responseCallback
            message['callbackId'] = callbackId
        }
        sendMessageQueue.push(message); //將字典放入數組
        //修改iframe標簽的src屬性,UIWebView監聽執行代理方法
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

    function _fetchQueue() {  //json數組轉成json字符串
        var messageQueueString = JSON.stringify(sendMessageQueue)
        sendMessageQueue = []
        return messageQueueString
    }

    function _dispatchMessageFromObjC(messageJSON) {
        setTimeout(function _timeoutDispatchMessageFromObjC() {
            var message = JSON.parse(messageJSON)
            var messageHandler

            if (message.responseId) {
                var responseCallback = responseCallbacks[message.responseId]
                if (!responseCallback) { return; }
                responseCallback(message.responseData)
                delete responseCallbacks[message.responseId]
            } else {
                var responseCallback
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId
                    responseCallback = function(responseData) {
                        _doSend({ responseId:callbackResponseId, responseData:responseData })
                    }
                }

                var handler = WebViewJavascriptBridge._messageHandler
                if (message.handlerName) {
                    handler = messageHandlers[message.handlerName]
                }

                try {
                    handler(message.data, responseCallback)
                } catch(exception) {
                    if (typeof console != 'undefined') {
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
                    }
                }
            }
        })
    }

    function _handleMessageFromObjC(messageJSON) {
        if (receiveMessageQueue) {
            receiveMessageQueue.push(messageJSON)
        } else {
            _dispatchMessageFromObjC(messageJSON)
        }
    }

    window.WebViewJavascriptBridge = {
        init: init,
        send: send,
        registerHandler: registerHandler,
        callHandler: callHandler,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    }

    var doc = document
    _createQueueReadyIframe(doc)
    var readyEvent = doc.createEvent('Events')
    readyEvent.initEvent('WebViewJavascriptBridgeReady')
    readyEvent.bridge = WebViewJavascriptBridge
    doc.dispatchEvent(readyEvent)
})();

View Code

2、UIWebView加載我們自定義的html頁面TestJSBridge.html, 里面有腳本注冊js調用oc方法標識,和oc調用js標識


<html>
    <head>
        <meta charset="utf-8"/>
        <style type="text/css">
            html { font-family:Helvetica; color:#222; background:#D5FFFD; border: 5px dashed blue;}
            .rowH3{margin: 0px; text-align: center;}
            .jsBtn{font-size: 18px;}
        </style>
    </head>
    <body>
        <h3 class="rowH3">測試OC和JS互相調用</h3>
        <button class="jsBtn" id="jsBtn">JS調用OC方法</button>
        <div id="logDiv"><!-- 打印日志 --></div>
    </body>
</html>
<script type="text/javascript">
    window.onerror = function(err) {
        printLog(err);
    }

    function connectWebViewJavascriptBridge(callback) {
        if (window.WebViewJavascriptBridge) {
            callback(WebViewJavascriptBridge);
        } else {
            document.addEventListener('WebViewJavascriptBridgeReady', function() {
                callback(WebViewJavascriptBridge);
            }, false);
        }
    }

    var uniqueId = 1;
    //日志打印方法
    function printLog(data) {
        var logObj = document.getElementById('logDiv');
        var el = document.createElement('div');
        el.className = 'logLine';
        el.innerHTML = uniqueId++ + ': ' + JSON.stringify(data); //json轉字符串
        if (logObj.children.length) { logObj.insertBefore(el, logObj.children[0]) }
        else { logObj.appendChild(el) }
    }

    //初始化調用函數connectWebViewJavascriptBridge
    connectWebViewJavascriptBridge(function(bridge) {

        bridge.init(function(message, responseCallback) {});  

        //注冊js響應方法,響應OC調用,標識objc_Call_JS_Func
        bridge.registerHandler('objc_Call_JS_Func', function(data, responseCallback) {
            printLog(data);  //打印oc傳過來的參數
        });

        //給標簽按鈕設置點擊事件
        var callbackButton = document.getElementById('jsBtn');
        callbackButton.onclick = function(e) {
            e.preventDefault();
            //注冊標識js_Call_Objc_Func,便于js給IOS發送消息
            bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我從js那邊過來!'},  function(response) { });
        }
    });

</script>

View Code

3、點擊html標簽按鈕,觸發js事件

//給標簽按鈕設置點擊事件
        var callbackButton = document.getElementById('jsBtn');
        callbackButton.onclick = function(e) {
            e.preventDefault();
            //注冊標識js_Call_Objc_Func,便于js給IOS發送消息
            bridge.callHandler('js_Call_Objc_Func', {id: 1, info: 'hello, iOS, 我從js那邊過來!'},  function(response) { });
        }

我們跟蹤bridge.callHandler方法,進入WebViewJavascriptBridge.js

var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'

var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'

messagingIframe是個iframe標簽,點擊我們自定義html按鈕標簽,觸發js事件,最后進入callHandler -->  _doSend ,

當messagingIframe標簽src重新賦值時,會觸發UIWebView的代理方法(src的值一直是:wvjbscheme:// __WVJB_QUEUE_MESSAGE__ ,也可自定義,這個在進入oc UIWebView代理方法時會用來作為判斷標識)。

跟蹤后面執行的過程:

至此,js調用oc成功

總結js調用oc過程:

-->   觸發js事件

-->   把要傳入參數和自定義注冊標識“ js_Call_Objc_Func” 存入js數組 sendMessageQueue

-->  重新賦值iframe標簽的src屬性,觸發UIWebView代理方法, 根據src的值進入相應處理方法中

-->    在oc方法里面調用js方法 _fetchQueue, 獲取js數組里面所有的參數  

-->   根據傳入的自定義注冊標識 js_Call_Objc_Func  從oc字典 _messageHandlers找出匹配block, 最后執行block,里面有我們自定義處理的后續代碼

二、oc調用js過程

從oc內部發起

-- > 調用bridge的 callHandler方法,傳入需要的參數和自定義注冊標識

--> 最后使用UIWebView系統方法 stringByEvaluatingJavaScriptFromString調用js腳本 WebViewJavascriptBridge._handleMessageFromObjC 完成參數的傳遞

---------------------------------------------  end --------------------------------------

DEMO下載

github地址: https://github.com/xiaotanit/Tan_WebViewJavaScriptBridge

另外記錄一個UIWebView不能加載帶中文參數的url問題:

假設加載url為: http://baidu.com/?search= 博客園

這樣UIWebView加載這個帶中文參數的url, 是不能顯示的,需要把中文進行轉義,才能顯示。

使用字符串方法 stringByAddingPercentEncodingWithAllowedCharacters對中文進行轉義

NSString *str = @"http://baidu.com/?search=博客園";
  //  str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  str = [str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];

 NSURL *url = [NSURL URLWithString:str];
  NSURLRequest *request = [NSURLRequest requestWithURL:url];

 

來自:http://www.cnblogs.com/tandaxia/p/5699886.html

 

 本文由用戶 BusterMcCra 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!