JavaScriptCore.framework可能

来源:互联网 发布:淘宝卖家邮费怎么便宜 编辑:程序博客网 时间:2024/05/29 17:36

转:http://justsee.iteye.com/blog/2036713 太长了


iOS7中新加入的JavaScriptCore.framework可能被大多数开发人员所忽略,但是如果你之前就在项目中用过自己编译JavaScriptCore来处理JavaScript,那么你需要重新关注一下JavaScriptCore.framework。

JavaScriptCore是苹果Safari浏览器的JavaScript引擎,或许你之前听过Google的V8引擎,在WWDC上苹果演示了最新的Safari,据说JavaScript处理速度已经大大超越了Google的Chrome,这就意味着JavaScriptCore在性能上也不输V8了。
其实JavaScriptCore.framework在OS X平台上很早就存在的,不过接口都是纯C语言的,而在iOS平台,苹果没有开放该framework,所以不少需要在iOS app中处理JavaScript的都得自己从开源的WebKit中编译出JavaScriptCore.a,接口也是纯C语言的。可能是苹果发现越来越多的程序使用了自编译的JavaScriptCore,干脆做个顺水人情将JavaScriptCore.framework开放了,同时还提供了Objective-C的接口。
Objetive-C -> JavaScript
@import JavaScriptCore;

int main() {
    JSContext *context = [[JSContext alloc] init];
    JSValue *result = [context evaluateScript:@"2 + 2"];
    NSLog(@"2 + 2 = %d", [result toInt32]);
    return 0;
}
这里就需要介绍一下概念了,首先是JSContext,一个Context就是一个JavaScript代码执行的环境,也叫作用域。既然是作用域,那作用域可以是有大有小的:
var globalVar = "level0"
function fun1(){
    var value1 = "level1";
    var fun2 = function(){
        var value2 = "level2";
    }
}
在上面的JS代码中,一共有三个JSContext,最外层的Context包含globalVar对象和fun1函数,其实该层Context包含一个隐性的对象,叫做:GlobalObject(在浏览器环境下该对象就是Window),所有属于该Context的对象其实是GloalObject的属性。fun1函数内属于第二个Context,fun2内为第三个Context。我们只能在相应的Context下去执行对应的代码段。也就是你不能用最外层的JSContext直接调用evaluateScript方法执行fun2函数。但是不管有多少个Context,他们的GlobalObject都是指向的一个对象。
大家知道JS里面是弱类型的,也就是只有在代码执行时才能知道一个变量具体是什么类型,而Objective-C是强类型了,为了处理这种类型差异,JSValue就被引入了。下面是Objective-C和JavaScript中类型的对照表:
Objective-C type JavaScript type
nil undefined
NSNull null
NSString string
NSNumber number, boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock * Function object *
id ** Wrapper object **
Class * Constructor object *
JSValue的作用就是在Objective-C对象和JavaScript对象之间起转换作用:
//covert Objective-C Object to JavaScript Object
JSValue *jsObject = [JSValue valueWithObject:objcObject inContext:context];
//Covert JavaScript Object to Objective-C Object
id objcObject = [jsObject toObject];
更多关于在Objective-C环境下调用JavaScript的实例代码,推荐查看WebKit开源项目中JavaScriptCore的单元测试代码: https://github.com/WebKit/webkit/blob/master/Source/JavaScriptCore/API/tests/testapi.mm
JavaScript -> Objective-C
可以通过两种方式在JavaScript中调用Objective-C:
Blocks: 对应JS函数
JSExport协议: 对应JS对象
Blocks
context[@"makeUIColor"] = ^(NSDictionary *rgbColor){
    float red = [rgbColor[@"red"] floatValue];
    float green = [rgbColor[@"green"] floatValue];
    float blue = [rgbColor[@"blue"] floatValue];
    return [UIColor colorWithRed:(red / 255.0)
                           green:(green / 255.0)
                            blue:(blue / 255.0)
                           alpha:1];
};
JSValue *color = [context evaluateScript:@"makeUIColor({red: 50, green: 150, blue: 250})"];
NSLog(@"color:%@",[color toObject]);
通过Blocks实现JS调用Objective-C时有两点需要注意的问题:
不要在Block中直接引用使用外面的JSContext对象,如果想获取当前的Context对象,应该用[JSContext currentContext];,这样来避免循引用问题。
不要在Block中直接使用外面的JSValue对象,如果需要,把JSValue当做参数来传进Block中。
JSExport
JSExport是一个协议,很方便的让JavaScript能够访问和操作Objective-C对象。
#import <objc/runtime.h>
@import JavaScriptCore;
@protocol UIButtonExport <JSExport>
- (void)setTitle:(NSString *)title forState:(UIControlState)state;
@end

- (void)viewDidLoad{
    [super viewDidLoad]
    class_addProtocol([UIButton class], UIButtonExpert);

    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button setTitle:@"Hello Objective-C" forState:UIControlStateNormal];
    button.frame = CGRectMake(20, 40, 280, 40);
    [self.view addSubview:button];

    JSContext *context = [[JSContext alloc] init];
    context[@"button"] = button;
    [context evaluateScript:@"button.setTitleForState('Hello JavaScript', 0)"];
}
上面代码中,我们申明一个UIButtonExport协议,该协议继承于JSExport,并将setTitle:forState:方法开放到该协议中(只有JSExport协议中的方法才能被JavaScript识别),然后通过运行时让UIButton遵循UIButtonExport协议。这样你就可以在JS中为Button设置title了,需要说明一点的是,在JS中方法的命名规则与Objective-C中有点不一样,如Objective-C中的方法-(void)setX:(id)x Y:(id)y Z:(id)z;,加入到JSExport协议中,在JS中调用就得是setXYZ(x, y, z);,当然如果你不想根据这种命名转换规则,你也可以通过JSExport.h中的方法来修改:
#define JSExportAs(PropertyName, Selector) \
    @optional Selector __JS_EXPORT_AS__##PropertyName:(id)argument; @required Selector
#endif
如setX:Y:Z方法,我们可以给他重命名,让JS中通过set3D(x,y,z)来调用
JSExportAs(set3D,
     - (void)setX:(id)x Y:(id)y Z:(id)z
);
思考: 理论上我们可以通过运行时,让Foundation和UIKit等framework中所有的类的属性和方法遵循JSExport协议,这样就可以直接在JS中使用这些Objective-C的类。
内存管理
Objective-C使用ARC,在JavaScript中使用是垃圾回收,并且在JS中所有的引用都是强引用(strong),当然JavaScriptCore新增的Obj-C的接口为你省去了很多处理,你在使用的时候只需要注意两点就行了:
将JSValue对象存储到Objective-C对象中;
将JS字段添加到Objective-C对象。
function ClickHandler(button, callback) {
     this.button = button;
     this.button.onClickHandler = this;
     this.handleEvent = callback;
};
在上面的js代码中,我们为button添加onclick处理事件,在Objective-C对用的Button类中,我们需要保存该onclick handler,以便在按钮点击时调用该handler。
@implementation MyButton
- (void)setOnClickHandler:(JSValue *)handler
{
     _onClickHandler = handler; // Retain cycle
}
@end
如果我们直接来保存到handler,就会出现内存泄露,因为JS中引用button对象是强引用,如果Button也用强引用来保存JS中的handler,这就导致了Retain cycle。我们没法改变JavaScript中的强引用机制,只能在Objective-C中来处理,没错,在Objective-C中弱引用js handler,但是弱引用handler,万一在我点击Button调用click事件时, onclick handler已经被释放了怎么办? 来看看JavaScriptCore是怎么做的:
@implementation MyButton
- (void)setOnClickHandler:(JSValue *)handler
{
     _onClickHandler = [JSManagedValue managedValueWithValue:handler];
     [_context.virtualMachine addManagedReference:_onClickHandler
                                        withOwner:self]

@end
JavaScriptCore中引入了JSManagedValue类型,该类型主要是作为一个引用桥接,将JSValue转为JSManagedValue类型后,可以添加到JSVirtualMachine对象中,这样能够保证你在使用过程中JSValue对象不会被释放掉,当你不再需要该JSValue对象后,从JSVirtualMachine中移除该JSManagedValue对象,JSValue对象就会被释放并置空。
大家不要被这么多对象类型搞晕了,简单一点说,JSVirtualMachine就是一个用于保存弱引用对象的数组,加入该数组的弱引用对象因为会被该数组retain,所以保证了使用时不会被释放,当数组里的对象不再需要时,就从数组中移除,没有了引用的对象就会被系统释放。
到这里要介绍的东西就差不多了,苹果这次开放了JavaScriptCore,其实给程序开发提供了无限的可能,Objective-C和JavaScript相结合,也一定能够产生出更多的开发模式。如果想继续了解JavaScriptCore,再次推荐看看WebKit项目组JavaScriptCore单元测试用例, 还可以研究一下本文中没有介绍的JavaScriptCore的C接口。
0 0