http://www.cocoachina.com/ios/20160623/16796.html

iOS 开发中,我们时不时的需要加载一些 Web 页面,一些需求使用 Web 页面来实现可以更可控,如上线后也可以发布更新,修改 UI 布局,或者修复 bug,这些 Web 页面的作用不止是展示,很大一部分是需要和原生代码实现的 UI 和业务逻辑发生交互的,那么不可避免的,就需要用一些方法来实现 Web 页面(主要是 JavaScript)和原生代码之间的通信,在 JavaScriptCore 出现之前,很多项目都在用 WebViewJavascriptBridge 作为 Web 页面和原生代码之间的一个桥梁(bridge),来传输一些数据和方法的调用,如 Facebook MessengerFacebook 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 框架是很容易的实现两者之间的数据传输的。具体做法可以参考参考资料。

苹果添加的这些新特性可以给开发带来很多便利,就是不知道有坑没有,嗯,且爬且珍惜吧。

使用 JavaScriptCore 进制通讯的 demo 放到了 GitHub,地址如下:
https://github.com/cielpy/CPYJSCoreDemo

参考资料

【转】使用JavaScriptCore在JS和OC间通信的更多相关文章

  1. UIWebView的应用和其中的JS与OC间传值

    现在有很多的应用已经采用了WebView和html语言结合的开发模式.html5一直很火因为一份代码可以在多个平台上运用啊,效果各不相同都很美观,也越来越有一些公司直接招后台程序员和html5程序员, ...

  2. JS和OC间的通信(使用JavaScriptCore)

    JavaScriptCore 时代的通讯 iOS 7 开始,苹果提供了一个叫作 JavaScriptCore 的框架,使用 JavaScriptCore 框架可以实现 OC 和 JS 的互相调用,而不 ...

  3. iOS下JS与OC互相调用(四)--JavaScriptCore

    前面讲完拦截URL的方式实现JS与OC互相调用,终于到JavaScriptCore了.它是从iOS7开始加入的,用 Objective-C 把 WebKit 的 JavaScript 引擎封装了一下, ...

  4. iOS中JS 与OC的交互(JavaScriptCore.framework)

    iOS中实现js与oc的交互,目前网上也有不少流行的开源解决方案: 如:react native 当然一些轻量级的任务使用系统提供的UIWebView 以及JavaScriptCore.framewo ...

  5. iOS开发:JavaScriptCore.framework的简单使用--JS与OC的交互篇

    iOS7之后苹果为众猿推出了JavaScriptCore.framework这个框架,这个框架为大家在与JS交互上提供了很大帮助,可以在html界面上调用OC方法并传参,也可以在OC上调用JS方法并传 ...

  6. 转载 【iOS开发】网页JS与OC交互(JavaScriptCore) OC ----->JS

      目标 本文介绍利用苹果在iOS7时发布的JavaScriptCore.framework框架进行js与OC的交互.我们想要达到的目标是: OC调用网页上的js方法 网页js调用APP中的OC方法 ...

  7. JS调用OC方法并传值,OC调用JS方法并传值////////////////////////zz

     iOS开发-基于原生JS与OC方法互相调用并传值(附HTML代码)     最近项目里面有有个商品活动界面,要与web端传值,将用户在网页点击的商品id 传给客户端,也就是js交互,其实再说明白一点 ...

  8. 史上最全的 UIWebview 的 JS 与 OC 交互

    来源:伯乐在线 - 键盘风筝 链接:http://ios.jobbole.com/89330/ 点击 → 申请加入伯乐在线专栏作者 其实一直想给大家整理一下JS与OC的交互,但是没有合适的机会,今天借 ...

  9. JS 与 OC

    做项目需要从网页点击跳转到app的一个页面上,并且需要获取参数. 当时后台给写的参数是这样的.自己打开浏览器看的源码 JavaScript:window.location.href=   这句话在js ...

随机推荐

  1. linux下操作PostgreSQL的常用命令

    一般性: \copyright 显示PostgreSQL的使用和发行许可条款          \g [文件] or; 执行查询 (并把结果写入文件或 |管道) \h [名称] SQL命令语法上的说明 ...

  2. TeeChart显示三维的图形,使用Surface

    绘制一个球 根据公式x^2+y^2+z^2=R^2; 令x=RsinAcosB  y=RcosAcosB z=RsinB using System; using System.Collections. ...

  3. [POJ3279]Fliptile(开关问题,枚举)

    题目链接:http://poj.org/problem?id=3279 题解:http://www.cnblogs.com/helenawang/p/5538547.html /* ━━━━━┒ギリギ ...

  4. bzoj2792

    首先想到二分答案是吧,设为lim 这道题难在判定,我们先不管将一个数变为0的条件 先使序列满足相邻差<=lim,这个正着扫一遍反着扫一遍即可 然后我们就要处理将一个数变为0的修改代价 当i变为0 ...

  5. poj3683 Priest John's Busiest Day

    2-SAT. 读入用了黄学长的快速读入,在此膜拜感谢. 把每对时间当作俩个点.如果有交叉代表相互矛盾. 然后tarjan缩点,这样就能得出当前的2-SAT问题是否有解. 如果有解,跑拓扑排序就能找出一 ...

  6. bzoj1934: [Shoi2007]Vote 善意的投票

    最大流..建图方式都是玄学啊.. //Dinic是O(n2m)的. #include<cstdio> #include<cstring> #include<cctype& ...

  7. rsync不存在用户处理CPU消耗拒绝服务漏洞

    受影响产品: rsync 3.1.0 漏洞描述: CVE ID:CVE-2014-2855 rsync是一款文件同步管理软件. rsync处理不存在用户时存在安全漏洞,可消耗大量CPU资源,造成拒绝服 ...

  8. Android Studio 我常用快捷键

    0. Ctrl+Alt+L 格式化代码 Ctrl+Alt+O 优化导入的类 1. 重载方法 Ctrl+O 2.Ctrl+shift+Enter:自动匹配相对应的语法结构,比如if,do-while,t ...

  9. DataTables ajax重新加载数据

    传数据给后台返回数据,最开始的办法是 重新生成一个datatable对象,但是在每次点击刷新时都会有闪动的现象,而且代价很高.理想中应该仅仅更新数据. 最后在文档中查到一个插件fnReloadAjax ...

  10. Liunx 配置IDE

    如果你还没装编译环境或自己不确定装没装,不妨先执行 sudo apt-get install build-essential 如果你已经了解一些 vim 的知识,而且想用它来编辑源代码,那么我们不妨装 ...