javascriptcore库的使用详解
2016-02-18 21:07
627 查看
以前想要oc调用js代码一般是通过uiwebview的stringByEvaluatingJavaScriptFromString方法,或是直接使用JavaScriptCore引擎,例如https://github.com/jfahrenkrug/AddressBookSpy。
这个框架其实只是基于webkit中以C/C++实现的JavaScriptCore的一个包装,在旧版本iOS开发中,很多开发者也会自行将webkit的库引入项目编译使用。不过虽然iOS7把它当成了标准库,可惜目前,我还没有在Apple Developer中找到像之前文章中收集的那样的官方文档介绍这个框架的具体使用方法。
好在还可以在Xcode中找到头文件,而且里面的注释对每个类和方法的功能写得还算清楚,再结合网上仅有的几篇文章的介绍,我也在此简单入门一下JavaScriptCore。更多细节还待开发过程中发现。
在项目中引入JavaScriptCore后,链到头文件中,除了大段的Copyright注释可以看到里面只要引入了5个文件,每个文件里都定义跟文件名对应的类:
JSContext
JSValue
JSManagedValue
JSVirtualMachine
JSExport
虽说代码中的注释介绍的也比较详细了,但是如同一图顶万言,对程序员来说代码更有说服力。本文就先来说说这些类相对比较好理解但又很常用的JSContext和JSValue以及它们方法的使用方式和效果。
Objective-C JavaScript JSValue Convert JSValue Constructor
先看个简单的例子:
JSContext *context = [[JSContext
alloc] init];
JSValue *jsVal = [context
evaluateScript:@"21+7"];
int iVal = [jsVal
toInt32];
NSLog(@"JSValue:
%@, int: %d", jsVal, iVal);
//Output:
// JSValue: 28, int: 28
很简单吧,还可以存一个JavaScript变量在
JSContext *context = [[JSContext
alloc] init];
[context evaluateScript:@"var
arr = [21, 7 , 'iderzheng.com'];"];
1b023
JSValue *jsArr = context[@"arr"]; //
Get array from JSContext
NSLog(@"JS
Array: %@; Length: %@", jsArr, jsArr[@"length"]);
jsArr[1] = @"blog"; //
Use JSValue as array
jsArr[7] = @7;
NSLog(@"JS
Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);
NSArray *nsArr = [jsArr
toArray];
NSLog(@"NSArray:
%@", nsArr);
//Output:
// JS Array: 21,7,iderzheng.com Length: 3
// JS Array: 21,blog,iderzheng.com,,,,,7 Length: 8
// NSArray: (
// 21,
// blog,
// "iderzheng.com",
// "<null>",
// "<null>",
// "<null>",
// "<null>",
// 7
// )
通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且
各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。比如在前端开发中常用的
JSContext *context = [[JSContext
alloc] init];
context[@"log"] = ^() {
NSLog(@"+++++++Begin
Log+++++++");
NSArray *args = [JSContext
currentArguments];
for (JSValue *jsVal in args) {
NSLog(@"%@",
jsVal);
}
JSValue *this = [JSContext
currentThis];
NSLog(@"this:
%@",this);
NSLog(@"-------End
Log-------");
};
[context evaluateScript:@"log('ider',
[7, 21], { hello:'world', js:100 });"];
//Output:
// +++++++Begin Log+++++++
// ider
// 7,21
// [object Object]
// this: [object GlobalObject]
// -------End Log-------
通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,
Block可以传入
JSContext *context = [[JSContext
alloc] init];
[context evaluateScript:@"function
add(a, b) { return a + b; }"];
JSValue *add = context[@"add"];
NSLog(@"Func:
%@", add);
JSValue *sum = [add
callWithArguments:@[@(7),
@(21)]];
NSLog(@"Sum:
%d",[sum toInt32]);
//OutPut:
// Func: function add(a, b) { return a + b; }
// Sum: 28
Objective-C的异常会在运行时被Xcode捕获,而在
^(JSContext *context,
JSValue *exceptionValue) {
context.exception = exceptionValue;
};
我们也可以给
JSContext *context = [[JSContext
alloc] init];
context.exceptionHandler = ^(JSContext *con,
JSValue *exception) {
NSLog(@"%@",
exception);
con.exception = exception;
};
[context evaluateScript:@"ider.zheng
= 21"];
//Output:
// ReferenceError: Can't find variable: ider
从之前的例子和介绍应该有体会到Block在JavaScriptCore中起到的强大作用,它在JavaScript和Objective-C之间的转换 建立起更多的桥梁,让互通更方便。但是要注意的是无论是把Block传给
比如上边自定义异常处理方法,就是赋给传入
Reference)使得内存无法正常释放。
对于
(
前面十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。本文将扩展到更复杂的类型,介绍一下该强大的框架是如何让Objective-C对象和JavaScript对象进行直接互通的。
为了方便起见,以下所有代码中的JSContext对象都会添加如下的
JSContext *context = [[JSContext
alloc] init];
context.exceptionHandler = ^(JSContext *con,
JSValue *exception) {
NSLog(@"%@",
exception);
con.exception = exception;
};
context[@"log"] = ^() {
NSArray *args = [JSContext
currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};
JSContext并不能让Objective-C和JavaScript的对象直接转换,毕竟两者的面向对象的设计方式是不同的:前者基于
JSValue *obj =[context
evaluateScript:@"var
jsObj = { number:7, name:'Ider' }; jsObj"];
NSLog(@"%@,
%@", obj[@"name"],
obj[@"number"]);
NSDictionary *dic = [obj
toDictionary];
NSLog(@"%@,
%@", dic[@"name"],
dic[@"number"]);
//Output:
// Ider, 7
// Ider, 7
同样的,
NSDictionary *dic = @{@"name": @"Ider", @"#":@(21)};
context[@"dic"] = dic;
[context evaluateScript:@"log(dic.name,
dic['#'])"];
//OutPut:
// Ider
// 21
JavaScript可以脱离
@protocol PersonProtocol <JSExport>
@property (nonatomic,
retain) NSDictionary *urls;
- (NSString *)fullName;
@end
@interface Person :NSObject <PersonProtocol>
@property (nonatomic,
copy) NSString *firstName;
@property (nonatomic,
copy) NSString *lastName;
@end;
@implementation Person
@synthesize firstName, lastName, urls;
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@
%@", self.firstName, self.lastName];
}
@end
在上边的代码中,定义了一个
下边就来创建一个
// initialize person object
Person *person = [[Person
alloc] init];
context[@"p"] = person;
person.firstName = @"Ider";
person.lastName = @"Zheng";
person.urls = @{@"site": @"http://www.iderzheng.com"};
// ok to get fullName
[context evaluateScript:@"log(p.fullName());"];
// cannot access firstName
[context evaluateScript:@"log(p.firstName);"];
// ok to access dictionary as object
[context evaluateScript:@"log('site:',
p.urls.site, 'blog:', p.urls.blog);"];
// ok to change urls property
[context evaluateScript:@"p.urls
= {blog:'http://blog.iderzheng.com'}"];
[context evaluateScript:@"log('-------AFTER
CHANGE URLS-------')"];
[context evaluateScript:@"log('site:',
p.urls.site, 'blog:', p.urls.blog);"];
// affect on Objective-C side as well
NSLog(@"%@",
person.urls);
//Output:
// Ider Zheng
// undefined
// undefined
// site:
// http://www.iderzheng.com
// blog:
// undefined
// -------AFTER CHANGE URLS-------
// site:
// undefined
// blog:
// http://blog.iderzheng.com
// {
// blog = "http://blog.iderzheng.com";
// }
从输出结果不难看出,当访问
对于多参数的方法,JavaScriptCore的转换方式将Objective-C的方法每个部分都合并在一起,冒号后的字母变为大写并移除冒号。比如下边协议中的方法,在JavaScript调用就是:
@protocol MultiArgs <JSExport>
- (void)doFoo:(id)foo
withBar:(id)bar;
@end
如果希望方法在JavaScript中有一个比较短的名字,就需要用的JSExport.h中提供的宏:
@protocol LongArgs <JSExport>
JSExportAs(testArgumentTypes,
- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d
boolean:(BOOL)b string:(NSString *)s
number:(NSNumber *)n
array:(NSArray *)a
dictionary:(NSDictionary *)o
);
@end
比如上边定义的协议中的方法,在JavaScript就只要用
虽然JavaScriptCore框架还没有官方编程指南,但是在JSExport.h文件中对神秘协议的表述还是比较详细的,其中有一条是这样描述的:
By default no methods or properties of the Objective-C class will be exposed to JavaScript, however methods and properties may explicitly be exported. For each protocol that a class conforms to, if the protocol incorporates the protocol JSExport, then the protocol
will be interpreted as a list of methods and properties to be exported to JavaScript.
这里面有个incorporate一词值得推敲,经过验证只有直接继承了
对于自定义的Objective-C类,可以通过之前的方式自定义继承了
比如下边的例子,就是为
@protocol JSUITextFieldExport <JSExport>
@property(nonatomic,copy) NSString *text;
@end
之后在通过
- (void)viewDidLoad {
[super viewDidLoad];
textField.text = @"7";
class_addProtocol([UITextField
class], @protocol(JSUITextFieldExport));
}
为一个
- (IBAction)pressed:(id)sender {
JSContext *context = [[JSContext
alloc] init];
context[@"textField"] = textField;
NSString *script = @"var
num = parseInt(textField.text, 10);"
"++num;"
"textField.text = num;";
[context evaluateScript:script];
}
当运行点击UIButton时就会看到
虽然Objetive-C和JavaScript都是面向对象的语言,而且它们都可以让程序员专心于业务逻辑,不用担心内存回收的问题。但是两者的内存回首机制全是不同的,Objective-C是基于引用计数,之后Xcode编译器又支持了自动引用计数(ARC, Automatic Reference
Counting);JavaScript则如同Java/C#那样用的是垃圾回收机制(GC, Garbage Collection)。当两种不同的内存回收机制在同一个程序中被使用时就难免会产生冲突。
比如,在一个方法中创建了一个临时的Objective-C对象,然后将其加入到
同样的,如果用
怎么在两种内存回收机制中处理好对象内存就成了问题。JavaScriptCore提供了
@interface JSManagedValue : NSObject
// Convenience method for creating JSManagedValues from JSValues.
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value;
// Create a JSManagedValue.
- (id)initWithValue:(JSValue *)value;
// Get the JSValue to which this JSManagedValue refers. If the JavaScript value has been collected,
// this method returns nil.
- (JSValue *)value;
@end
在《iOS7新JavaScriptCore框架入门介绍》有提到
@interface JSVirtualMachine : NSObject
// Create a new JSVirtualMachine.
- (id)init;
// addManagedReference:withOwner and removeManagedReference:withOwner allow
// clients of JSVirtualMachine to make the JavaScript runtime aware of
// arbitrary external Objective-C object graphs. The runtime can then use
// this information to retain any JavaScript values that are referenced
// from somewhere in said object graph.
//
// For correct behavior clients must make their external object graphs
// reachable from within the JavaScript runtime. If an Objective-C object is
// reachable from within the JavaScript runtime, all managed references
// transitively reachable from it as recorded with
// addManagedReference:withOwner: will be scanned by the garbage collector.
//
- (void)addManagedReference:(id)object
withOwner:(id)owner;
- (void)removeManagedReference:(id)object
withOwner:(id)owner;
@end
对于iOS7提供JavaScriptCore已经介绍的差不多了,之前也提到这其实是一个开源的框架,所以如果想要在低版本的iOS上使用,也可以很容易地自行添加源码进行编译和使用。
阅读源码也可以更加了解JavaScriptCore是怎么实现的,在开发时候也可以注意到更多的细节避免错误的发生,想要阅读框架的源码可以在这里(源码1,源码2,源码3)。
文章中的代码和例子都比较简单,如果想了解更多JavaScriptCore的使用方法,在这里有详细的测试案例可以提供一些线索。不过经验证并不是所有的测试案例在iOS7中都会通过,这大概是测试案例所用的JavaScriptCore是为chromium实现的而iOS7是webkit吧。
References:
Steamclock Software – Apple’s new Objective-C to Javascript Bridge
JavaScriptCore and iOS 7 » Big Nerd Ranch BlogBig Nerd Ranch Blog
IOS7开发~JavaScriptCore (二) – 阿福的专栏 – 博客频道 – CSDN.NET
API in trunk/Source/JavaScriptCore – WebKit
Objective-C Runtime Reference
Automatic Reference Counting vs. Garbage Collection – The Oxygene Language Wiki
http://www.oschina.net/translate/how-to-choose-a-javascript-engine-for-ios-and-android-apps?cmp
感谢:http://blog.iderzheng.com/introduction-to-ios7-javascriptcore-framework/
http://justsee.iteye.com/blog/2036713
Steamclock Software – Apple’s new Objective-C to Javascript Bridge
JavaScriptCore and iOS 7 » Big Nerd Ranch BlogBig Nerd Ranch Blog
IOS7开发~JavaScriptCore (二) – 阿福的专栏 – 博客频道 – CSDN.NET
API in trunk/Source/JavaScriptCore – WebKit
Objective-C Runtime Reference
Automatic Reference Counting vs. Garbage Collection – The Oxygene Language Wiki
http://www.oschina.net/translate/how-to-choose-a-javascript-engine-for-ios-and-android-apps?cmp
感谢:http://blog.iderzheng.com/introduction-to-ios7-javascriptcore-framework/
但是,Apple正式发布了新的iOS 7系统,引入了JavaScriptCore.framework
,最大最直观的改变在于界面变得小清新范了,我也提到《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中的类
在项目中引入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 |
基本类型转换
先看个简单的例子:JSContext *context = [[JSContext
alloc] init];
JSValue *jsVal = [context
evaluateScript:@"21+7"];
int iVal = [jsVal
toInt32];
NSLog(@"JSValue:
%@, int: %d", jsVal, iVal);
//Output:
// JSValue: 28, int: 28
很简单吧,还可以存一个JavaScript变量在
JSContext中,然后通过下标来获取出来。而对于
Array或者
Object类型,
JSValue也可以通过下标直接取值和赋值。
JSContext *context = [[JSContext
alloc] init];
[context evaluateScript:@"var
arr = [21, 7 , 'iderzheng.com'];"];
1b023
JSValue *jsArr = context[@"arr"]; //
Get array from JSContext
NSLog(@"JS
Array: %@; Length: %@", jsArr, jsArr[@"length"]);
jsArr[1] = @"blog"; //
Use JSValue as array
jsArr[7] = @7;
NSLog(@"JS
Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);
NSArray *nsArr = [jsArr
toArray];
NSLog(@"NSArray:
%@", nsArr);
//Output:
// JS Array: 21,7,iderzheng.com Length: 3
// JS Array: 21,blog,iderzheng.com,,,,,7 Length: 8
// NSArray: (
// 21,
// blog,
// "iderzheng.com",
// "<null>",
// "<null>",
// "<null>",
// "<null>",
// 7
// )
通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且
JSValue是遵循JavaScript的数组特性:无下标越位,自动延展数组大小。并且通过
JSValue还可以获取JavaScript对象上的属性,比如例子中通过
"length"就获取到了JavaScript数组的长度。在转成
NSArray的时候,所有的信息也都正确转换了过去。
方法的转换
各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。比如在前端开发中常用的log方法,虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:
JSContext *context = [[JSContext
alloc] init];
context[@"log"] = ^() {
NSLog(@"+++++++Begin
Log+++++++");
NSArray *args = [JSContext
currentArguments];
for (JSValue *jsVal in args) {
NSLog(@"%@",
jsVal);
}
JSValue *this = [JSContext
currentThis];
NSLog(@"this:
%@",this);
NSLog(@"-------End
Log-------");
};
[context evaluateScript:@"log('ider',
[7, 21], { hello:'world', js:100 });"];
//Output:
// +++++++Begin Log+++++++
// ider
// 7,21
// [object Object]
// this: [object GlobalObject]
// -------End Log-------
通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,
JSContext提供了类方法来获取参数列表(
+ (JSContext *) currentArguments;)和当前调用该方法的对象(
+ (JSValue *)currentThis)。对于
"this",输出的内容是
GlobalObject,这也是
JSContext对象方法
- (JSValue *)globalObject;所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。
Block可以传入
JSContext作方法,但是
JSValue没有
toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是
JSValue提供了
- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反过来将参数传进去来调用方法。
JSContext *context = [[JSContext
alloc] init];
[context evaluateScript:@"function
add(a, b) { return a + b; }"];
JSValue *add = context[@"add"];
NSLog(@"Func:
%@", add);
JSValue *sum = [add
callWithArguments:@[@(7),
@(21)]];
NSLog(@"Sum:
%d",[sum toInt32]);
//OutPut:
// Func: function add(a, b) { return a + b; }
// Sum: 28
JSValue还提供
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在
JSContext的
globalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的
JSValue对象调用。
异常处理
Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被
JSContext捕获并存储在
exception属性上,而不会向外抛出。时时刻刻检查
JSContext对象的
exception是否不为
nil显然是不合适,更合理的方式是给
JSContext对象设置
exceptionHandler,它接受的是
^(JSContext *context, JSValue *exceptionValue)形式的Block。其默认值就是将传入的
exceptionValue赋给传入的
context的
exception属性:
^(JSContext *context,
JSValue *exceptionValue) {
context.exception = exceptionValue;
};
我们也可以给
exceptionHandler赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:
JSContext *context = [[JSContext
alloc] init];
context.exceptionHandler = ^(JSContext *con,
JSValue *exception) {
NSLog(@"%@",
exception);
con.exception = exception;
};
[context evaluateScript:@"ider.zheng
= 21"];
//Output:
// 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同样也会形成引用循环。
前面十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。本文将扩展到更复杂的类型,介绍一下该强大的框架是如何让Objective-C对象和JavaScript对象进行直接互通的。
为了方便起见,以下所有代码中的JSContext对象都会添加如下的
log方法和
eventHandler:
JSContext *context = [[JSContext
alloc] init];
context.exceptionHandler = ^(JSContext *con,
JSValue *exception) {
NSLog(@"%@",
exception);
con.exception = exception;
};
context[@"log"] = ^() {
NSArray *args = [JSContext
currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};
键值对编程—Dictionary
JSContext并不能让Objective-C和JavaScript的对象直接转换,毕竟两者的面向对象的设计方式是不同的:前者基于class,后者基于
prototype。但所有的对象其实可以视为一组键值对的集合,所以JavaScript中的对象可以返回到Objective-C中当做
NSDictionary类型进行访问。
JSValue *obj =[context
evaluateScript:@"var
jsObj = { number:7, name:'Ider' }; jsObj"];
NSLog(@"%@,
%@", obj[@"name"],
obj[@"number"]);
NSDictionary *dic = [obj
toDictionary];
NSLog(@"%@,
%@", dic[@"name"],
dic[@"number"]);
//Output:
// Ider, 7
// Ider, 7
同样的,
NSDicionary和
NSMutableDictionary传入到JSContext之后也可以直接当对象来调用:
NSDictionary *dic = @{@"name": @"Ider", @"#":@(21)};
context[@"dic"] = dic;
[context evaluateScript:@"log(dic.name,
dic['#'])"];
//OutPut:
// Ider
// 21
语言穿梭机—JSExport协议
JavaScript可以脱离prototype继承完全用JSON来定义对象,但是Objective-C编程里可不能脱离类和继承了写代码。所以JavaScriptCore就提供了
JSExport作为两种语言的互通协议。
JSExport中没有约定任何的方法,连可选的(
@optional)都没有,但是所有继承了该协议(
@protocol)的协议(注意不是Objective-C的类(@interface))中定义的方法,都可以在
JSContext中被使用。语言表述起来有点绕,还是用例子来说明会更明确一点。
@protocol PersonProtocol <JSExport>
@property (nonatomic,
retain) NSDictionary *urls;
- (NSString *)fullName;
@end
@interface Person :NSObject <PersonProtocol>
@property (nonatomic,
copy) NSString *firstName;
@property (nonatomic,
copy) NSString *lastName;
@end;
@implementation Person
@synthesize firstName, lastName, urls;
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@
%@", self.firstName, self.lastName];
}
@end
在上边的代码中,定义了一个
PersonProtocol,并让它继承了神秘的
JSExport协议,在新定义的协议中约定
urls属性和
fullName方法。之后又定义了
Person类,除了让它实现
PersonProtocol外,还定义了firstName和lastName属性。而fullName方法返回的则是两部分名字的结合。
下边就来创建一个
Person对象,然后传入到
JSContext中并尝试使用JavaScript来访问和修改该对象。
// initialize person object
Person *person = [[Person
alloc] init];
context[@"p"] = person;
person.firstName = @"Ider";
person.lastName = @"Zheng";
person.urls = @{@"site": @"http://www.iderzheng.com"};
// ok to get fullName
[context evaluateScript:@"log(p.fullName());"];
// cannot access firstName
[context evaluateScript:@"log(p.firstName);"];
// ok to access dictionary as object
[context evaluateScript:@"log('site:',
p.urls.site, 'blog:', p.urls.blog);"];
// ok to change urls property
[context evaluateScript:@"p.urls
= {blog:'http://blog.iderzheng.com'}"];
[context evaluateScript:@"log('-------AFTER
CHANGE URLS-------')"];
[context evaluateScript:@"log('site:',
p.urls.site, 'blog:', p.urls.blog);"];
// affect on Objective-C side as well
NSLog(@"%@",
person.urls);
//Output:
// Ider Zheng
// undefined
// undefined
// site:
// http://www.iderzheng.com
// blog:
// undefined
// -------AFTER CHANGE URLS-------
// site:
// undefined
// blog:
// http://blog.iderzheng.com
// {
// blog = "http://blog.iderzheng.com";
// }
从输出结果不难看出,当访问
firstName和
lastName的时候给出的结果是
undefined,因为它们跟JavaScript没有
JSExport的联系。但这并不影响从
fullName()中正确得到两个属性的值。和之前说过的一样,对于
NSDictionary类型的
urls,可以在
JSContext中当做对象使用,而且还可以正确地给
urls赋予新的值,并反映到实际的Objective-C的
Person对象上。
JSExport不仅可以正确反映属性到JavaScript中,而且对属性的特性也会保证其正确,比如一个属性在协议中被声明成
readonly,那么在JavaScript中也就只能读取属性值而不能赋予新的值。
对于多参数的方法,JavaScriptCore的转换方式将Objective-C的方法每个部分都合并在一起,冒号后的字母变为大写并移除冒号。比如下边协议中的方法,在JavaScript调用就是:
doFooWithBar(foo, bar);
@protocol MultiArgs <JSExport>
- (void)doFoo:(id)foo
withBar:(id)bar;
@end
如果希望方法在JavaScript中有一个比较短的名字,就需要用的JSExport.h中提供的宏:
JSExportAs(PropertyName, Selector)。
@protocol LongArgs <JSExport>
JSExportAs(testArgumentTypes,
- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d
boolean:(BOOL)b string:(NSString *)s
number:(NSNumber *)n
array:(NSArray *)a
dictionary:(NSDictionary *)o
);
@end
比如上边定义的协议中的方法,在JavaScript就只要用
testArgumentTypes(i, d, b, s, n, a, dic);来调用就可以了。
虽然JavaScriptCore框架还没有官方编程指南,但是在JSExport.h文件中对神秘协议的表述还是比较详细的,其中有一条是这样描述的:
By default no methods or properties of the Objective-C class will be exposed to JavaScript, however methods and properties may explicitly be exported. For each protocol that a class conforms to, if the protocol incorporates the protocol JSExport, then the protocol
will be interpreted as a list of methods and properties to be exported to JavaScript.
这里面有个incorporate一词值得推敲,经过验证只有直接继承了
JSExport的自定义协议(
@protocol)才能在
JSContext中访问到。也就是说比如有其它的协议继承了上边的
PersonProtocol,其中的定义的方法并不会被引入到
JSContext中。从源码中也能看出JavaScriptCore框架会通过
class_copyProtocolList方法找到类所遵循的协议,然后再对每个协议通过
protocol_copyProtocolList检查它是否遵循JSExport协议进而将方法反映到JavaScript之中。
对已定义类扩展协议— class_addProtocol
对于自定义的Objective-C类,可以通过之前的方式自定义继承了JSExport的协议来实现与JavaScript的交互。对于已经定义好的系统类或者从外部引入的库类,她们都不会预先定义协议提供与JavaScript的交互的。好在Objective-C是可以在运行时实行对类性质的修改的。
比如下边的例子,就是为
UITextField添加了协议,让其能在JavaScript中可以直接访问
text属性。该接口如下:
@protocol JSUITextFieldExport <JSExport>
@property(nonatomic,copy) NSString *text;
@end
之后在通过
class_addProtocol为其添加上该协议:
- (void)viewDidLoad {
[super viewDidLoad];
textField.text = @"7";
class_addProtocol([UITextField
class], @protocol(JSUITextFieldExport));
}
为一个
UIButton添加如下的事件,其方法只要是将
textField传入到
JSContext中然后读取其
text值,自增1后重新赋值:
- (IBAction)pressed:(id)sender {
JSContext *context = [[JSContext
alloc] init];
context[@"textField"] = textField;
NSString *script = @"var
num = parseInt(textField.text, 10);"
"++num;"
"textField.text = num;";
[context evaluateScript:script];
}
当运行点击UIButton时就会看到
UITextField的值在不断增加,也证明了对于已定义的类,也可以在运行时添加神奇的
JSExport协议让它们可以在Objective-C和JavaScript直接实现友好互通。
不同内存管理机制—Reference Counting vs. Garbage Collection
虽然Objetive-C和JavaScript都是面向对象的语言,而且它们都可以让程序员专心于业务逻辑,不用担心内存回收的问题。但是两者的内存回首机制全是不同的,Objective-C是基于引用计数,之后Xcode编译器又支持了自动引用计数(ARC, Automatic ReferenceCounting);JavaScript则如同Java/C#那样用的是垃圾回收机制(GC, Garbage Collection)。当两种不同的内存回收机制在同一个程序中被使用时就难免会产生冲突。
比如,在一个方法中创建了一个临时的Objective-C对象,然后将其加入到
JSContext放在JavaScript中的变量中被使用。因为JavaScript中的变量有引用所以不会被释放回收,但是Objective-C上的对象可能在方法调用结束后,引用计数变0而被回收内存,因此JavaScript层面也会造成错误访问。
同样的,如果用
JSContext创建了对象或者数组,返回
JSValue到Objective-C,即使把
JSValue变量
retain下,但可能因为JavaScript中因为变量没有了引用而被释放内存,那么对应的
JSValue也没有用了。
怎么在两种内存回收机制中处理好对象内存就成了问题。JavaScriptCore提供了
JSManagedValue类型帮助开发人员更好地管理对象内存。
@interface JSManagedValue : NSObject
// Convenience method for creating JSManagedValues from JSValues.
+ (JSManagedValue *)managedValueWithValue:(JSValue *)value;
// Create a JSManagedValue.
- (id)initWithValue:(JSValue *)value;
// Get the JSValue to which this JSManagedValue refers. If the JavaScript value has been collected,
// this method returns nil.
- (JSValue *)value;
@end
在《iOS7新JavaScriptCore框架入门介绍》有提到
JSVirtualMachine为整个JavaScriptCore的执行提供资源,所以当将一个
JSValue转成
JSManagedValue后,就可以添加到
JSVirtualMachine中,这样在运行期间就可以保证在Objective-C和JavaScript两侧都可以正确访问对象而不会造成不必要的麻烦。
@interface JSVirtualMachine : NSObject
// Create a new JSVirtualMachine.
- (id)init;
// addManagedReference:withOwner and removeManagedReference:withOwner allow
// clients of JSVirtualMachine to make the JavaScript runtime aware of
// arbitrary external Objective-C object graphs. The runtime can then use
// this information to retain any JavaScript values that are referenced
// from somewhere in said object graph.
//
// For correct behavior clients must make their external object graphs
// reachable from within the JavaScript runtime. If an Objective-C object is
// reachable from within the JavaScript runtime, all managed references
// transitively reachable from it as recorded with
// addManagedReference:withOwner: will be scanned by the garbage collector.
//
- (void)addManagedReference:(id)object
withOwner:(id)owner;
- (void)removeManagedReference:(id)object
withOwner:(id)owner;
@end
了解更多更多—Source Code
对于iOS7提供JavaScriptCore已经介绍的差不多了,之前也提到这其实是一个开源的框架,所以如果想要在低版本的iOS上使用,也可以很容易地自行添加源码进行编译和使用。阅读源码也可以更加了解JavaScriptCore是怎么实现的,在开发时候也可以注意到更多的细节避免错误的发生,想要阅读框架的源码可以在这里(源码1,源码2,源码3)。
文章中的代码和例子都比较简单,如果想了解更多JavaScriptCore的使用方法,在这里有详细的测试案例可以提供一些线索。不过经验证并不是所有的测试案例在iOS7中都会通过,这大概是测试案例所用的JavaScriptCore是为chromium实现的而iOS7是webkit吧。
References:
Steamclock Software – Apple’s new Objective-C to Javascript Bridge
JavaScriptCore and iOS 7 » Big Nerd Ranch BlogBig Nerd Ranch Blog
IOS7开发~JavaScriptCore (二) – 阿福的专栏 – 博客频道 – CSDN.NET
API in trunk/Source/JavaScriptCore – WebKit
Objective-C Runtime Reference
Automatic Reference Counting vs. Garbage Collection – The Oxygene Language Wiki
http://www.oschina.net/translate/how-to-choose-a-javascript-engine-for-ios-and-android-apps?cmp
感谢:http://blog.iderzheng.com/introduction-to-ios7-javascriptcore-framework/
http://justsee.iteye.com/blog/2036713
Steamclock Software – Apple’s new Objective-C to Javascript Bridge
JavaScriptCore and iOS 7 » Big Nerd Ranch BlogBig Nerd Ranch Blog
IOS7开发~JavaScriptCore (二) – 阿福的专栏 – 博客频道 – CSDN.NET
API in trunk/Source/JavaScriptCore – WebKit
Objective-C Runtime Reference
Automatic Reference Counting vs. Garbage Collection – The Oxygene Language Wiki
http://www.oschina.net/translate/how-to-choose-a-javascript-engine-for-ios-and-android-apps?cmp
感谢:http://blog.iderzheng.com/introduction-to-ios7-javascriptcore-framework/
相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- 2019年开发人员应该学习的8个JavaScript框架
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因