文档章节

Objc与JS间相互调用

fengwenjie
 fengwenjie
发布于 2015/06/26 10:21
字数 1050
阅读 1020
收藏 3

过去3、4年都在进行跨平台的混合应用开发,但一直没有系统梳理跨平台技术的底层原理,趁新工作未正式入职,这里整理一下。

跨平台的一种实现是基于webview。所谓webview,实质是在原生app中打开一个内嵌浏览器,具体到iOS平台就是使用UIWebView这个控件。然后就很容易理解了,我们相当于开发一个webapp(网页应用),然后通过原生应用作为用户入口(而非原生浏览器),用户会访问到远程服务器的网页内容。从用户感知上,似乎是在使用一个App,但实际上是在访问一个网页。

以上,只是跨平台基于webview实现的工作原理,而更重要的是如何桥接webview的js和app的objc,使得webapp也可用使用原生的功能api,如调用摄像头等,而app又可以调用webview里的js,即向双通信。

Apple开放了一个叫做JavascriptCore的框架,此框架最早在OSX10.2就存在,但到了2013年在OSX10.9上才发布其调用的API,而后又在iOS7上公开,由此我们可用名正言顺地使用了。

JavascriptCore提供了以下几个API,实现跨平台通信:

  • JavascriptCore/API/JSContext.h
  • JavascriptCore/API/JSExport.h
  • JavascriptCore/API/JSValue.h

JSContext是JavascriptCore的主入口,它代表了JS的运行时环境,在其中可以定义对象、方法等,这些实体(对象、方法)的生命周期在JSContext被释放的时候才结束。而且可用指定的JSVirtualMachine来创建JSContext,每个JSVM都会独立运行在一个线程上。

我们可用通过JSContext的evaluateScript方法来定义我们的JS方法,而且是通过字符串定义代码,当然可以通过读取外边js文件来实现。看下面的例子:

        // getting a JSContext
        JSContext *context = [JSContext new];
    
        // defining a JavaScript function
        NSString *jsFunctionText =
        @"var isValidNumber = function(phone) {"
        "    var phonePattern = /^[0-9]{3}[ ][0-9]{3}[-][0-9]{4}$/;"
        "    return phone.match(phonePattern) ? true : false;"
        "}";
        [context evaluateScript:jsFunctionText];

这里,相当于在objc层,向JSContext注入了一个isValidNumber的js方法。

正如上文所述,JSContext代表了一个JS运行环境,而我们的示例代码都是单独创建这个JSContext运行环境的,实际上UIWebView实例也有它自己的JSContext运行环境。为了修改web上的内容,我们需要访问UIWebView的JSContext。

但Apple就是一个闷骚男,虽然已经公开了JavascriptCore的API,但又不提供直接访问UIWebView’s JSContext的方法。

幸好“key-value”把我们救了回来:

// get JSContext from UIWebView instance
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

然后,我们可以通过JSValue来获取JSContext中js方法的引用和执行的结果:

        // 获取isValidNumber方法的引用
        JSValue *jsFunction = context[@"isValidNumber"];
        // 通过callWithArguments方法调用js方法
        JSValue *value = [jsFunction callWithArguments:@[ phone ]];

JavascriptCore会自动转换JSValue的对象类型,比如这里isValidNumber返回的boolean,同时还支持NSString, NSDate, NSDictionary, NSArray等。

另外,我们还可以增加异常捕捉

[context setExceptionHandler:^(JSContext *context, JSValue *value) {
        NSLog(@"%@", value);
}];

再有,通过JSExport可以将objc的方法暴露给JS。

  @protocol BNRContactAppJS <JSExport>
    
    - (void)addContact:(BNRContact *)contact;
    
    @end
    
    @interface BNRContactApp : NSObject <BNRContactAppJS>
    ...
    @end

addContact这个方法是在BNRContactAppJS协议中声明的,BNRContactAppJS又源自于JSExport,所以addContact方法将会暴露给JS环境,而其他方法则对JS环境而言是隐藏的。

最后,我们看一个完整的例子

- (void)webViewDidFinishLoad:(UIWebView *)webView
    {
        // get JSContext from UIWebView instance
        JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
        // enable error logging
        [context setExceptionHandler:^(JSContext *context, JSValue *value) {
            NSLog(@"WEB JS: %@", value);
        }];
    
        // give JS a handle to our BNRContactApp instance
        context[@"myApp"] = self.app;
    
        // register BNRContact class
        context[@"BNRContact"] = [BNRContact class];
    
        // add function for processing form submission
        NSString *addContactText =
        @"var contactForm = document.forms[0];"
         "var addContact = function() {"
         "    var name = contactForm.name.value;"
         "    var phone = contactForm.phone.value;"
         "    var address = contactForm.address.value;"
         "    var contact = BNRContact.contactWithNamePhoneAddress(name, phone, address);"
         "    myApp.addContact(contact);"
         "};"
         "contactForm.addEventListener('submit', addContact);";
        [context evaluateScript:addContactText];
    }

最终跨平台调用就在这一句:

myApp.addContact(contact);

在JS环境调用了objc的方法。

总结一下:

  • ==从objc调用js:JSContext的evaluateScriptf方法和JSValue的callWithArguments方法;==
  • ==捕捉JS执行的异常;==
  • ==从WebView实例获取JSContext;==
  • ==通过JSExport将objc方法暴露给js调用。==

最后啰嗦一下,iOS7以前,并没有JavascriptCore,所以多使用 stringByEvaluatingJavaScriptFromString。

Titanium 就是使用了JavascriptCore的方式。

© 著作权归作者所有

共有 人打赏支持
fengwenjie
粉丝 6
博文 24
码字总数 20311
作品 0
白云
JSPatch库, 一个Apple官方支持的实现在线更新iOS应用的库

简介 项目主页: https://github.com/bang590/JSPatch 示例下载: https://github.com/ios122/ios122 JSPatch 可以让你用 JavaScript 书写原生 iOS APP。只需在项目引入极小的引擎,就可以使用...

ios122
2015/11/10
0
0
iOS WKWebView和JS交互的两种方式

本文介绍两种方式实现iOS WKWebView和JS交互 WKWebViewConfiguration注入WKScriptMessageHandler UIWebViewDelegate回调方法中处理 WKWebViewConfiguration注入WKScriptMessageHandler 网页很......

aron1992
2017/02/18
0
0
iframe 与主框架相互访问方法

1.同域相互访问 假设A.html 与 b.html domain都是localhost (同域) A.html中iframe 嵌入 B.html,name=myframe A.html有js function fMain() B.html有js function fIframe() 需要实现 A.ht......

lsjane
2015/08/20
0
0
JavaScript (js) 和Flex交互总结(IE&&非IE浏览器)

首先,不管是Flex调用js,还是js调用 Flex 需要引入的包(Flex包)import flash.external.*; 其次,简单说说相互之间如何调用 1、Flex程序调用js的方法 : 这个相对简单,如下 Flex中: publ...

soul_mate
2014/06/23
0
1
asp.net中调用javascript自定义函数的方法(包括引入JavaScript文件)总结

通常javascript代码可以与HTML标签一起直接放在前端页面中,但如果JS代码多的话一方面不利于维护,另一方面也对搜索引擎不友好,因为页面因此而变得臃肿;所以一般有良好开发习惯的程序员都会...

黄献
2012/11/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JS:异步 - 面试惨案

为什么会写这篇文章,很明显不符合我的性格的东西,原因是前段时间参与了一个面试,对于很多程序员来说,面试时候多么的鸦雀无声,事后心里就有多么的千军万马。去掉最开始毕业干了一年的Jav...

xmqywx
今天
0
0
Win10 64位系统,PHP 扩展 curl插件

执行:1. 拷贝php安装目录下,libeay32.dll、ssleay32.dll 、 libssh2.dll 到 C:\windows\system32 目录。2. 拷贝php/ext目录下, php_curl.dll 到 C:\windows\system32 目录; 3. p...

放飞E梦想O
今天
0
0
谈谈神秘的ES6——(五)解构赋值【对象篇】

上一节课我们了解了有关数组的解构赋值相关内容,这节课,我们接着,来讲讲对象的解构赋值。 解构不仅可以用于数组,还可以用于对象。 let { foo, bar } = { foo: "aaa", bar: "bbb" };fo...

JandenMa
今天
1
0
OSChina 周一乱弹 —— 有人要给本汪介绍妹子啦

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @莱布妮子 :分享水木年华的单曲《中学时代》@小小编辑 手机党少年们想听歌,请使劲儿戳(这里) @须臾时光:夏天还在做最后的挣扎,但是晚上...

小小编辑
今天
21
5
centos7安装redis及开机启动

配置编译环境: sudo yum install gcc-c++ 下载源码: wget http://download.redis.io/releases/redis-3.2.8.tar.gz 解压源码: tar -zxvf redis-3.2.8.tar.gz 进入到解压目录: cd redis-3......

hotsmile
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部