WebViewJavascriptBridge源码探究--看OC和JS交互过程

      今天把实现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)
})();

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>

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];

另外记录一下:UIWebView不能监听加载的html页面a标签进行相对链接跳转。

举例说明,比如加载的html页面有个a标签链接:

<a href="/index.html">去首页</a>

这种跳转UIWebViewDelegate的代理方法监听不到

原文链接:http://www.cnblogs.com/tandaxia/p/5699886.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员宝库

10 种最常见的 Javascript 错误

英文:SKOWRONSKI 译文:elevenbeans elevenbeans.github.io/2018/02/05/top-10-javascript...

39780
来自专栏九彩拼盘的叨叨叨

学习纲要:异步流程处理

修改上面的代码,用 Promise,async/await,事件发布订阅 这几种方式实现下面的需求

10620
来自专栏拂晓风起

cocos2d-js 自定义事件监听派发

18230
来自专栏软件开发

JavaScript学习总结(五)——jQuery插件开发与发布

jQuery插件就是以jQuery库为基础衍生出来的库,jQuery插件的好处是封装功能,提高了代码的复用性,加快了开发速度,现在网络上开源的jQuery插件非...

13030
来自专栏圣杰的专栏

ABP入门系列(14)——应用BootstrapTable表格插件

源码路径:Github-LearningMpaAbp 1. 引言 之前的文章ABP入门系列(7)——分页实现讲解了如何进行分页展示,但其分页展示仅适用于前台we...

88950
来自专栏技术墨客

React学习(7)—— 高阶应用:性能优化 原

在React内部已经使用了许多巧妙的技术来最小化由于Dom变更导致UI渲染所耗费的时间。对于很多应用来说,使用React后无需太多工作就会让客户端执行性能有质的...

14920
来自专栏程序你好

1000个项目中前10名的JavaScript错误介绍

7710
来自专栏互联网杂技

React -- 组件间通信

分为三种类型的通信关系: 1、父组件向子组件通信 2、子组件向父组件通信 3、没有嵌套关系的组件之间的通信 父组件向子组件通信 父组件通过子组件的prop...

46570
来自专栏GIS讲堂

lzugis——Arcgis Server for JavaScript API之POI

POI(Point Of Interest),感兴趣点,其实呢,严格意义上说应该不是POI,但是单位就这样叫了,我也就这样叫了,其实现的功能大致是这样的:用过百...

13320
来自专栏Modeng的专栏

Vue双向绑定原理,教你一步一步实现双向绑定

当今前端天下以 Angular、React、vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋。

15310

扫码关注云+社区

领取腾讯云代金券