OC与JS交互(JavaScriptCore框架入门介绍)

前阵子,Apple正式发布了新的iOS 7系统,最大最直观的改变在于界面变得小清新范了,我也提到《iOS,你真的越来越像Android了》。不过对于移动开发者来说,除了要适应Xcode 5,最应该关注的还是iOS 7在开发接口的上的变化。概览Apple提供的官方文档《What’s
New in iOS》
,最最让我欣喜的是iOS 7中加入了JavaScriptCore框架。该框架让Objective-C和JavaScript代码直接的交互变得更加的简单方便。

这个框架其实只是基于webkit中以C/C++实现的JavaScriptCore的一个包装,在旧版本iOS开发中,很多开发者也会自行将webkit的库引入项目编译使用。不过虽然iOS7把它当成了标准库,可惜目前,我还没有在Apple Developer中找到像之前文章中收集的那样的官方文档介绍这个框架的具体使用方法。

好在还可以在Xcode中找到头文件,而且里面的注释对每个类和方法的功能写得还算清楚,再结合网上仅有的几篇文章的介绍,我也在此简单入门一下JavaScriptCore框架在基本数据类型上的使用。在另一篇《JavaScriptCore框架在iOS7中的对象交互和管理》中,我更深入地介绍该框架在对象上的使用。

JavaScriptCore中的类

在项目中引入JavaScriptCore后,链到头文件中,除了大段的Copyright注释可以看到里面只要引入了5个文件,每个文件里都定义跟文件名对应的类:

  • JSContext
  • JSValue
  • JSManagedValue
  • JSVirtualMachine
  • JSExport

虽说代码中的注释介绍的也比较详细了,但是如同一图顶万言,对程序员来说代码更有说服力。本文就先来说说这些类相对比较好理解但又很常用的JSContext和JSValue以及它们方法的使用方式和效果。

JSContext和JSValue

JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext就为其提供着运行环境,通过- (JSValue *)evaluateScript:(NSString *)script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。而JSContext的创建都是基于JSVirtualMachine-
(id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
,如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。

JSValue则可以说是JavaScript和Object-C之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。其一一对应方式可见下表:

Objective-C JavaScript JSValue Convert JSValue Constructor
nil undefined   valueWithUndefinedInContext
NSNull null   valueWithNullInContext:
NSString string toString  
NSNumber number, boolean toNumber

toBool

toDouble

toInt32

toUInt32

valueWithBool:inContext:

valueWithDouble:inContext:

valueWithInt32:inContext:

valueWithUInt32:inContext:

NSDictionary Object object toDictionary valueWithNewObjectInContext:
NSArray Array object toArray valueWithNewArrayInContext:
NSDate Date object toDate  
NSBlock Function object    
id Wrapper object toObject

toObjectOfClass:

valueWithObject:inContext:
Class Constructor object    

基本类型转换

先看个简单的例子:

1     JSContext *context = [[JSContext alloc] init];
2     JSValue *jsVal = [context evaluateScript:@"21+7"];
3     int iVal = [jsVal toInt32];
4     NSLog(@"JSValue: %@, int: %d", jsVal, iVal);
5
6     //Output:
7     // JSValue: 28, int: 28

很简单吧,还可以存一个JavaScript变量在JSContext中,然后通过下标来获取出来。而对于Array或者Object类型,JSValue也可以通过下标直接取值和赋值。

 1     JSContext *context = [[JSContext alloc] init];
 2     [context evaluateScript:@"var arr = [21, 7 , ‘iderzheng.com‘];"];
 3     JSValue *jsArr = context[@"arr"]; // Get array from JSContext
 4
 5     NSLog(@"JS Array: %@; Length: %@", jsArr, jsArr[@"length"]);
 6     jsArr[1] = @"blog"; // Use JSValue as array
 7     jsArr[7] = @7;
 8
 9     NSLog(@"JS Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);
10
11     NSArray *nsArr = [jsArr toArray];
12     NSLog(@"NSArray: %@", nsArr);
13
14     //Output:
15     // JS Array: 21,7,iderzheng.com Length: 3
16     // JS Array: 21,blog,iderzheng.com,,,,,7 Length: 8
17     // NSArray: (
18     // 21,
19     // blog,
20     // "iderzheng.com",
21     // "<null>",
22     // "<null>",
23     // "<null>",
24     // "<null>",
25     // 7
26     // )

通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且JSValue是遵循JavaScript的数组特性:无下标越位,自动延展数组大小。并且通过JSValue还可以获取JavaScript对象上的属性,比如例子中通过"length"就获取到了JavaScript数组的长度。在转成NSArray的时候,所有的信息也都正确转换了过去。

方法的转换

各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。比如在前端开发中常用的log方法,虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:

 1     JSContext *context = [[JSContext alloc] init];
 2     context[@"log"] = ^() {
 3     NSLog(@"+++++++Begin Log+++++++");
 4
 5     NSArray *args = [JSContext currentArguments];
 6     for (JSValue *jsVal in args) {
 7     NSLog(@"%@", jsVal);
 8     }
 9
10     JSValue *this = [JSContext currentThis];
11     NSLog(@"this: %@",this);
12     NSLog(@"-------End Log-------");
13     };
14
15     [context evaluateScript:@"log(‘ider‘, [7, 21], { hello:‘world‘, js:100 });"];
16
17     //Output:
18     // +++++++Begin Log+++++++
19     // ider
20     // 7,21
21     // [object Object]
22     // this: [object GlobalObject]
23     // -------End Log-------

通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表(+ (JSContext *)currentContext;)和当前调用该方法的对象(+ (JSValue *)currentThis)。对于"this",输出的内容是GlobalObject,这也是JSContext对象方法-
(JSValue *)globalObject;
所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。

Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反过来将参数传进去来调用方法。

 1     JSContext *context = [[JSContext alloc] init];
 2     [context evaluateScript:@"function add(a, b) { return a + b; }"];
 3     JSValue *add = context[@"add"];
 4     NSLog(@"Func: %@", add);
 5
 6     JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
 7     NSLog(@"Sum: %d",[sum toInt32]);
 8     //OutPut:
 9     // Func: function add(a, b) { return a + b; }
10     // Sum: 28

JSValue还提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContextglobalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue对象调用。

异常处理

Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置exceptionHandler,它接受的是^(JSContext
*context, JSValue *exceptionValue)
形式的Block。其默认值就是将传入的exceptionValue赋给传入的contextexception属性:

1     ^(JSContext *context, JSValue *exceptionValue) {
2         context.exception = exceptionValue;
3     };

我们也可以给exceptionHandler赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:

 1     JSContext *context = [[JSContext alloc] init];
 2     context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
 3         NSLog(@"%@", exception);
 4         con.exception = exception;
 5     };
 6
 7     [context evaluateScript:@"ider.zheng = 21"];
 8
 9     //Output:
10     // ReferenceError: Can‘t find variable: ider

使用Block的注意事项

从之前的例子和介绍应该有体会到Block在JavaScriptCore中起到的强大作用,它在JavaScript和Objective-C之间的转换 建立起更多的桥梁,让互通更方便。但是要注意的是无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+
(JSContext *)currentContext;
来获得。否则会造成循环引用使得内存无法被正确释放。

比如上边自定义异常处理方法,就是赋给传入JSContext对象con,而不是其外创建的context对象,虽然它们其实是同一个对象。这是因为Block会对内部使用的在外部定义创建的对象做强引用,而JSContext也会对被赋予的Block做强引用,这样它们之间就形成了循环引用(Circular
Reference
)使得内存无法正常释放。

对于JSValue也不能直接从外部引用到Block中,因为每个JSValue上都有JSContext的引用 (@property(readonly, retain) JSContext *context;),JSContext再引用Block同样也会形成引用循环。

欢迎继续阅读《JavaScriptCore框架在iOS7中的对象交互和管理》了解更多JavaScriptCore框架

References:

  1. Execute Javascript in iOS Applications
  2. JavaScriptCore and iOS 7 ? Big Nerd Ranch BlogBig Nerd Ranch Blog
  3. Steamclock Software – Apple’s new Objective-C to Javascript Bridge
  4. Programming with Objective-C: Working with Blocks
  5. Integrating JavaScript into Native Applications – Tracy E – 推酷
时间: 2024-10-05 10:59:20

OC与JS交互(JavaScriptCore框架入门介绍)的相关文章

OC与JS交互之WebViewJavascriptBridge

上一篇文章介绍了通过UIWebView实现了OC与JS交互的可能性及实现的原理,并且简单的实现了一个小的示例DEMO,当然也有一部分遗留问题,使用原生实现过程比较繁琐,代码难以维护.这篇文章主要介绍下开源库WebViewJavascriptBridge的实现原理和使用方法,并用此开源库重写之前的示例,同样,本文的示例代码我会在文章后面给出欢迎star 我们在上一篇文章结尾处简要介绍了WebViewJavascriptBridge的实现原理也是基于UIWebView的协议拦截,通过阅读源码发现,中

JavaScript:浅谈iOS与H5的交互-JavaScriptCore框架

JavaScript:浅谈iOS与H5的交互-JavaScriptCore框架 前言 小的作为一个iOS程序猿,可能研究JavaScript以及H5相关的知识并不是为了真正的要去转行做这一方面,其实更多的为了要研究OC中的JavaScriptCore框架,JavaScriptCore框架主要是用来实现iOS与H5的交互.当然了,最早我曾经写过iOS浅谈webView的JS一种用法.(去掉web显示页的广告),这种算得上最简单的iOS与H5的交互了.现在混合编程越来越多,H5的相对讲多,所以研究J

iOS开发——网络开发OC篇&amp;OC与JS交互简单案例

OC与JS交互简单案例 网页开发中三个重要的知识,这里就不详细介绍了! Html:页面代码 Css:样式 javascript:响应 先来看一段html的简单代码,里面涉及了上面的三个部分(很简单) 1 <html> 2 <!--描述网页信息--> 3 <head> 4 <meta charset="UTF-8"/> 5 <title>hello world</title> 6 <script> 7 f

oc与js交互-----WKWebView

这些天研究了一些oc与js交互的问题,纯属个人理解 oc与js交互是跨平台iOS与javaScript的通信.公司为了开发节约成本,本来安卓,与iOS两个版本的app开发,利用js通过webView的加载只需要做一些交互工作就使得javaScript书写的页面可以同时在安卓与iOS上使用可谓一举两得. WKWebView里边的方法 1.OC中调用js方法(我认为是oc向js中传值) 使用的是 <span style="background-color: rgb(255, 255, 255)

Mac Webview OC与JS交互实现

1.首先,需要定义一个JS可识别的变量(如external)用于OC与JS交互 - (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame { [self.windowScriptObject setValue:self forKey:@"external"]; } 2.JS调用OC代码 第一步.在OC中声明对JS开放的

OC 和 js 交互

首先导入 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #d12f1b } span.s1 { color: #78492a } span.s2 { } #import <JavaScriptCore/JavaScriptCore.h> 声明 对象 p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #bb2ca2 } spa

通过WebViewJavascriptBridge实现OC与JS交互

在.m方法当中,申明一个WebViewJavascriptBridge属性: 1 @interface ExampleAppViewController () 2 @property WebViewJavascriptBridge* bridge; 3 @end 4 5 @implementation ExampleAppViewController 6 7 8 - (void)viewDidLoad { 9 10 11 UIWebView* webView = [[UIWebView allo

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

今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stringByEvaluatingJavaScriptFromString函数 .现在主要是了解js是如何调用oc方法的,分享下探究过程. 源码不多,就一个头文件WebViewJavascriptBridge.h和实现文件WebViewJavascriptBridge.m, 和一个js文件,实现在js那边可以调用oc方法,也可以在oc里面调用js方法. 先

node.js 11 Web框架Express 介绍,安装,静态页面,路由

本文参考原文-http://bjbsair.com/2020-03-22/tech-info/2815/前面介绍了node.js的文件模块,http server以及静态网站的创建.有了这些知识作为基础,我们可以了解一下node.js的Web框架了. 从Java一路过来的朋友可能觉得Web框架还是比较重量级的,比如最初的Struts到后来的Spring,中间Apache组织也有过一些其他的模板框架,总体而言无论是从使用还是学习来讲,都是需要花费较多时间的.对于大型应用而言,这个投入是值得的.如果