`
啸笑天
  • 浏览: 3438037 次
  • 性别: Icon_minigender_1
  • 来自: China
社区版块
存档分类
最新评论

ios7 JavaScriptCore.framework

 
阅读更多

以前想要oc调用js代码一般是通过uiwebview的stringByEvaluatingJavaScriptFromString方法,或是直接使用JavaScriptCore引擎,例如https://github.com/jfahrenkrug/AddressBookSpy。

 

 

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

JavaScriptCore

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

好在还可以在Xcode中找到头文件,而且里面的注释对每个类和方法的功能写得还算清楚,再结合网上仅有的几篇文章的介绍,我也在此简单入门一下JavaScriptCore。更多细节还待开发过程中发现。

JavaScriptCore中的类

JavaScriptCore_Head
在项目中引入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 *) 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;方法可以反过来将参数传进去来调用方法。

  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同样也会形成引用循环。

 

 

前面十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。本文将扩展到更复杂的类型,介绍一下该强大的框架是如何让Objective-C对象和JavaScript对象进行直接互通的。

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

  1. JSContext *context = [[JSContext alloc] init];
  2. context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
  3. NSLog(@"%@", exception);
  4. con.exception = exception;
  5. };
  6.  
  7. context[@"log"] = ^() {
  8. NSArray *args = [JSContext currentArguments];
  9. for (id obj in args) {
  10. NSLog(@"%@",obj);
  11. }
  12. };



 

 

键值对编程—Dictionary

JSContext并不能让Objective-C和JavaScript的对象直接转换,毕竟两者的面向对象的设计方式是不同的:前者基于class,后者基于prototype。但所有的对象其实可以视为一组键值对的集合,所以JavaScript中的对象可以返回到Objective-C中当做NSDictionary类型进行访问。

  1. JSValue *obj =[context evaluateScript:@"var jsObj = { number:7, name:'Ider' }; jsObj"];
  2. NSLog(@"%@, %@", obj[@"name"], obj[@"number"]);
  3. NSDictionary *dic = [obj toDictionary];
  4. NSLog(@"%@, %@", dic[@"name"], dic[@"number"]);
  5. //Output:
  6. // Ider, 7
  7. // Ider, 7

同样的,NSDicionaryNSMutableDictionary传入到JSContext之后也可以直接当对象来调用:

  1. NSDictionary *dic = @{@"name": @"Ider", @"#":@(21)};
  2. context[@"dic"] = dic;
  3. [context evaluateScript:@"log(dic.name, dic['#'])"];
  4. //OutPut:
  5. // Ider
  6. // 21

 

语言穿梭机—JSExport协议

JavaScript可以脱离prototype继承完全用JSON来定义对象,但是Objective-C编程里可不能脱离类和继承了写代码。所以JavaScriptCore就提供了JSExport作为两种语言的互通协议。JSExport中没有约定任何的方法,连可选的(@optional)都没有,但是所有继承了该协议(@protocol)的协议(注意不是Objective-C的类(@interface))中定义的方法,都可以在JSContext中被使用。语言表述起来有点绕,还是用例子来说明会更明确一点。

  1. @protocol PersonProtocol <JSExport>
  2.  
  3. @property (nonatomic, retain) NSDictionary *urls;
  4. - (NSString *)fullName;
  5.  
  6. @end
  7.  
  8. @interface Person :NSObject <PersonProtocol>
  9.  
  10. @property (nonatomic, copy) NSString *firstName;
  11. @property (nonatomic, copy) NSString *lastName;
  12.  
  13. @end;
  14.  
  15. @implementation Person
  16.  
  17. @synthesize firstName, lastName, urls;
  18.  
  19. - (NSString *)fullName {
  20. return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
  21. }
  22.  
  23. @end

在上边的代码中,定义了一个PersonProtocol,并让它继承了神秘的JSExport协议,在新定义的协议中约定urls属性和fullName方法。之后又定义了Person类,除了让它实现PersonProtocol外,还定义了firstName和lastName属性。而fullName方法返回的则是两部分名字的结合。

下边就来创建一个Person对象,然后传入到JSContext中并尝试使用JavaScript来访问和修改该对象。

  1. // initialize person object
  2. Person *person = [[Person alloc] init];
  3. context[@"p"] = person;
  4. person.firstName = @"Ider";
  5. person.lastName = @"Zheng";
  6. person.urls = @{@"site": @"http://www.iderzheng.com"};
  7.  
  8. // ok to get fullName
  9. [context evaluateScript:@"log(p.fullName());"];
  10. // cannot access firstName
  11. [context evaluateScript:@"log(p.firstName);"];
  12. // ok to access dictionary as object
  13. [context evaluateScript:@"log('site:', p.urls.site, 'blog:', p.urls.blog);"];
  14. // ok to change urls property
  15. [context evaluateScript:@"p.urls = {blog:'http://blog.iderzheng.com'}"];
  16. [context evaluateScript:@"log('-------AFTER CHANGE URLS-------')"];
  17. [context evaluateScript:@"log('site:', p.urls.site, 'blog:', p.urls.blog);"];
  18.  
  19. // affect on Objective-C side as well
  20. NSLog(@"%@", person.urls);
  21.  
  22. //Output:
  23. // Ider Zheng
  24. // undefined
  25. // undefined
  26. // site:
  27. // http://www.iderzheng.com
  28. // blog:
  29. // undefined
  30. // -------AFTER CHANGE URLS-------
  31. // site:
  32. // undefined
  33. // blog:
  34. // http://blog.iderzheng.com
  35. // {
  36. // blog = "http://blog.iderzheng.com";
  37. // }

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

JSExport不仅可以正确反映属性到JavaScript中,而且对属性的特性也会保证其正确,比如一个属性在协议中被声明成readonly,那么在JavaScript中也就只能读取属性值而不能赋予新的值。

对于多参数的方法,JavaScriptCore的转换方式将Objective-C的方法每个部分都合并在一起,冒号后的字母变为大写并移除冒号。比如下边协议中的方法,在JavaScript调用就是:doFooWithBar(foo, bar);

  1. @protocol MultiArgs <JSExport>
  2. - (void)doFoo:(id)foo withBar:(id)bar;
  3. @end

如果希望方法在JavaScript中有一个比较短的名字,就需要用的JSExport.h中提供的宏:JSExportAs(PropertyName, Selector)

  1. @protocol LongArgs <JSExport>
  2.  
  3. JSExportAs(testArgumentTypes,
  4. - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d
  5. boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n
  6. array:(NSArray *)a dictionary:(NSDictionary *)o
  7. );
  8.  
  9. @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属性。该接口如下:

  1. @protocol JSUITextFieldExport <JSExport>
  2.  
  3. @property(nonatomic,copy) NSString *text;
  4.  
  5. @end

之后在通过class_addProtocol为其添加上该协议:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3.  
  4. textField.text = @"7";
  5. class_addProtocol([UITextField class], @protocol(JSUITextFieldExport));
  6. }

为一个UIButton添加如下的事件,其方法只要是将textField传入到JSContext中然后读取其text值,自增1后重新赋值:

  1. - (IBAction)pressed:(id)sender {
  2. JSContext *context = [[JSContext alloc] init];
  3.  
  4. context[@"textField"] = textField;
  5.  
  6. NSString *script = @"var num = parseInt(textField.text, 10);"
  7. "++num;"
  8. "textField.text = num;";
  9. [context evaluateScript:script];
  10. }

当运行点击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类型帮助开发人员更好地管理对象内存。

  1. @interface JSManagedValue : NSObject
  2.  
  3. // Convenience method for creating JSManagedValues from JSValues.
  4. + (JSManagedValue *)managedValueWithValue:(JSValue *)value;
  5.  
  6. // Create a JSManagedValue.
  7. - (id)initWithValue:(JSValue *)value;
  8.  
  9. // Get the JSValue to which this JSManagedValue refers. If the JavaScript value has been collected,
  10. // this method returns nil.
  11. - (JSValue *)value;
  12.  
  13. @end

在《iOS7新JavaScriptCore框架入门介绍》有提到JSVirtualMachine为整个JavaScriptCore的执行提供资源,所以当将一个JSValue转成JSManagedValue后,就可以添加到JSVirtualMachine中,这样在运行期间就可以保证在Objective-C和JavaScript两侧都可以正确访问对象而不会造成不必要的麻烦。

  1. @interface JSVirtualMachine : NSObject
  2.  
  3. // Create a new JSVirtualMachine.
  4. - (id)init;
  5.  
  6. // addManagedReference:withOwner and removeManagedReference:withOwner allow
  7. // clients of JSVirtualMachine to make the JavaScript runtime aware of
  8. // arbitrary external Objective-C object graphs. The runtime can then use
  9. // this information to retain any JavaScript values that are referenced
  10. // from somewhere in said object graph.
  11. //
  12. // For correct behavior clients must make their external object graphs
  13. // reachable from within the JavaScript runtime. If an Objective-C object is
  14. // reachable from within the JavaScript runtime, all managed references
  15. // transitively reachable from it as recorded with
  16. // addManagedReference:withOwner: will be scanned by the garbage collector.
  17. //
  18. - (void)addManagedReference:(id)object withOwner:(id)owner;
  19. - (void)removeManagedReference:(id)object withOwner:(id)owner;
  20.  
  21. @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
  7. 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/

分享到:
评论
1 楼 啸笑天 2014-03-26  
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接口。

相关推荐

    iOS:JavaScriptCore.framework的使用.OC篇

    iOS:JavaScriptCore.framework的使用.OC篇.包括OC调用js方法(可传参),JS调用OC方法(可传参)

    gop-ios-sdk:GTOnePass iOS SDK。 http

    Webkit.framework, JavascriptCore.framework 产品依赖 SDK 三方依赖 无 相关开发资料 条目 资源 SDK 下载 接口文档 或查看头文件注释 错误码 产品结构流程 , 安装 获取SDK 下载获取 git clone ...

    JS与OC的交互

    使用JavaScriptCore.framework使得js与oc交互

    JS与OC互调demo

    该文件李含有三个文件夹,分别是iOS7之前的旧方法实现的JS与OC之间的混合开发、iOS7之后的javascriptCore.framework框架、GitHub上的demo。

    Interpreter:适用于 iOS 7 的 Node.js 解释器

    Nodelike 是一个在 iOS 7 和 OS X Mavericks 上使用 JavaScriptCore.framework 实现大致与 Node.JS 兼容的接口的项目。 (JavaScriptCore 在 iOS 7 之前不可用,在 OS X 上,该项目广泛使用了新更新的仅限 10.9 的 ...

    Unicode-Zawgyi-Converter:用于 Mac 的 Unicode :left-right_arrow:Zawgyi 转换器。 也可以使用 iOS 的代码。 它正在使用 parabaik js

    JavaScriptCore.framework 用法 #import "parabaik.h" Zawgyi 转 Unicode [parabaik zawgyiToUni:@"ျမန္မာစာ"]; 对于 Unicode 转 Zawgyi [parabaik uniToZawgyi:@"မြန်မာစာ"];

    实现Node.JS解释器的应用功能

    This is an example project implementing a Node.JS interpreter as an iOS app, utilising the Nodelike framework. Nodelike is a project to implement a roughly Node.JS-compatible interface using ...

    IOS与网页JS交互详解及实例

    IOS与网页JS交互 ... 为此,我整理了三种最常用最便捷有效的OC与JS...1. 技术方案:使用JavaScriptCore.framework框架 2. 使用场景: 网页中代码中的某个方法,比如点击事件方法,将该方法的参数传值给OC,供OC使用。

    LiquidCore:适用于Android和iOS的Node.js虚拟机

    LiquidiOS还为Android开发人员提供了一种方便的方法,可以在其应用程序内部 ,因为iOS开发人员已经可以使用JavaScriptCore进行本机处理。安装步骤1:确保将项目配置为与npm一起npm 在项目的根目录中,您必须具有...

    JS-Call-OC:javascript 调用objective-c 方法 及传参。 最全交互方法请查看 github.comshaojiankuiiOS-WebView-JavaScript

    JS-Call-OCjavascript 调用objective-c 方法 及传参如果SDK版本大于IO7可以用下JavaScriptCore.framework 框架,进行与 ObJective-c的交互,下边是个demoJavaScriptCore.framework :iOS7 中新加入的框架,用来处理...

    JS与Native交互

    从ios7开始苹果公布了JavaScriptCore.framework 这个系统库,用来解决JS与OC的交互。 说明: JavaScriptCore是封装了JavaScript和Objective-C桥接的Objective-C API,只需要较少的的代码,就可以实现JavaScript与...

    JavaScriptCore-usage:JavaScriptCore-C接口详解,以及基本的使用方法

    JavaScriptCore Framework 是 iOS7 引入的新功能,其实就是基于 Webkit 中以 C/C++ 实现的 JavaScriptCore 的一个封装,大多数 iOS 比较熟悉的是它的 Objective-C API,可以用简洁的方式实现 JS 与Native 交互,其实...

    ios下OC与JS交互之WKWebView

    上一篇文章我们使用了JavaScriptCore框架重写了之前的示例,iOS8苹果偏爱HTML5,重构了UIWebVIew,给我们带来了WKWebView,使其性能、稳定性、功能大幅度提升,也更好的支持了HTML5的新特性。这篇文章就们就拿...

    h5余原生交互

    1、H5调原生:1》通过代理调原生 2》block调原生方法 2、javaScriptCore版原生调H5 1》用evaluateScript方法直接调用 2》objectForKeyedSubscript + callWithArguments 方法调用

Global site tag (gtag.js) - Google Analytics