JavaScriptCore框架在iOS7中的对象交互和管理

以前一篇的文章中已经简单入门了iOS7中新加的JavaScriptCore框架的基本用法,十分的简单方便并且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。本文将扩展到更复杂的类型,介绍一下该强大的框架是如何让Objective-C对象和JavaScript对象进行直接互通的。javascript

为了方便起见,如下全部代码中的JSContext对象都会添加以下的log方法和eventHandlerhtml

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类型进行访问。java

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

一样的,NSDicionaryNSMutableDictionary传入到JSContext以后也能够直接当对象来调用:ios

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中被使用。语言表述起来有点绕,仍是用例子来讲明会更明确一点。git

@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来访问和修改该对象。web

// initialize person objectPerson *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 wellNSLog(@"%@", 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";//  }

从输出结果不难看出,当访问firstNamelastName的时候给出的结果是undefined,由于它们跟JavaScript没有JSExport的联系。但这并不影响从fullName()中正确获得两个属性的值。和以前说过的同样,对于NSDictionary类型的urls,能够在JSContext中当作对象使用,并且还能够正确地给urls赋予新的值,并反映到实际的Objective-C的Person对象上。objective-c

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)dboolean:(BOOL)b string:(NSString *)s number:(NSNumber *)narray:(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直接实现友好互通。

iOS Simulator Screen shot Nov 3, 2013 2.57.19 PM iOS Simulator Screen shot Nov 3, 2013 2.57.29 PM

 

不一样内存管理机制—Reference Counting vs. Garbage Collection

虽然Objetive-C和JavaScript都是面向对象的语言,并且它们均可以让程序员专心于业务逻辑,不用担忧内存回收的问题。可是二者的内存回首机制全是不一样的,Objective-C是基于引用计数,以后Xcode编译器又支持了自动引用计数(ARC, Automatic Reference Counting);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:
  1. Steamclock Software – Apple’s new Objective-C to Javascript Bridge

  2. JavaScriptCore and iOS 7 » Big Nerd Ranch BlogBig Nerd Ranch Blog

  3. IOS7开发~JavaScriptCore (二) – 阿福的专栏 – 博客频道 – CSDN.NET

  4. API in trunk/Source/JavaScriptCore – WebKit

  5. Objective-C Runtime Reference

  6. Automatic Reference Counting vs. Garbage Collection – The Oxygene Language Wiki

相关文章
相关标签/搜索