之前一篇的文章中已经简单入门了iOS7中新加的JavaScriptCore框架的基本用法,十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。本文将扩展到更复杂的类型,介绍一下该强大的框架是如何让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. context[@"log"] = ^() {
  7. NSArray *args = [JSContext currentArguments];
  8. for (id obj in args) {
  9. NSLog(@"%@",obj);
  10. }
  11. };

键值对编程—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. @property (nonatomic, retain) NSDictionary *urls;
  3. - (NSString *)fullName;
  4. @end
  5. @interface Person :NSObject <PersonProtocol>
  6. @property (nonatomic, copy) NSString *firstName;
  7. @property (nonatomic, copy) NSString *lastName;
  8. @end;
  9. @implementation Person
  10. @synthesize firstName, lastName, urls;
  11. - (NSString *)fullName {
  12. return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
  13. }
  14. @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. // ok to get fullName
  8. [context evaluateScript:@"log(p.fullName());"];
  9. // cannot access firstName
  10. [context evaluateScript:@"log(p.firstName);"];
  11. // ok to access dictionary as object
  12. [context evaluateScript:@"log('site:', p.urls.site, 'blog:', p.urls.blog);"];
  13. // ok to change urls property
  14. [context evaluateScript:@"p.urls = {blog:'http://blog.iderzheng.com'}"];
  15. [context evaluateScript:@"log('-------AFTER CHANGE URLS-------')"];
  16. [context evaluateScript:@"log('site:', p.urls.site, 'blog:', p.urls.blog);"];
  17. // affect on Objective-C side as well
  18. NSLog(@"%@", person.urls);
  19. //Output:
  20. // Ider Zheng
  21. // undefined
  22. // undefined
  23. // site:
  24. // http://www.iderzheng.com
  25. // blog:
  26. // undefined
  27. // -------AFTER CHANGE URLS-------
  28. // site:
  29. // undefined
  30. // blog:
  31. // http://blog.iderzheng.com
  32. // {
  33. // blog = "http://blog.iderzheng.com";
  34. // }

从输出结果不难看出,当访问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. JSExportAs(testArgumentTypes,
  3. - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d
  4. boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n
  5. array:(NSArray *)a dictionary:(NSDictionary *)o
  6. );
  7. @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. @property(nonatomic,copy) NSString *text;
  3. @end

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

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

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

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

当运行点击UIButton时就会看到UITextField的值在不断增加,也证明了对于已定义的类,也可以在运行时添加神奇的JSExport协议让它们可以在Objective-C和JavaScript直接实现友好互通。

 

不同内存管理机制—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. // Convenience method for creating JSManagedValues from JSValues.
  3. + (JSManagedValue *)managedValueWithValue:(JSValue *)value;
  4. // Create a JSManagedValue.
  5. - (id)initWithValue:(JSValue *)value;
  6. // Get the JSValue to which this JSManagedValue refers. If the JavaScript value has been collected,
  7. // this method returns nil.
  8. - (JSValue *)value;
  9. @end

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

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

了解更多更多—Source Code

对于iOS7提供JavaScriptCore已经介绍的差不多了,之前也提到这其实是一个开源的框架,所以如果想要在低版本的iOS上使用,也可以很容易地自行添加源码进行编译和使用。

阅读源码也可以更加了解JavaScriptCore是怎么实现的,在开发时候也可以注意到更多的细节避免错误的发生,想要阅读框架的源码可以在这里(源码1,源码2,源码3)。

文章中的代码和例子都比较简单,如果想了解更多JavaScriptCore的使用方法,在这里有详细的测试案例可以提供一些线索。不过经验证并不是所有的测试案例在iOS7中都会通过,这大概是测试案例所用的JavaScriptCore是为chromium实现的而iOS7是webkit吧。

转载于:https://www.cnblogs.com/luqinbin/p/5556222.html

JavaScriptCore框架在iOS7中的对象交互和管理相关推荐

  1. 【面向对象】面向对象程序设计测试题5-Java中的对象交互测试题

    Test5:Java中的对象交互测试题 问题 1 Look the inheritance relation: In a source of java have the following line: ...

  2. 【面向对象】面向对象程序设计测试题6-Java中的对象交互强化测试题

    Test6:Java对象之间的交互强化测试题 问题 1 假如有一个Student类,其定义如下: public Class Student{private int ID;private String ...

  3. html window 属性,html中window对象top 、self 、parent 等属性

    top 属性返回最顶层的先辈窗口. 该属性返回对一个顶级窗口的只读引用.如果窗口本身就是一个顶级窗口,top 属性存放对窗口自身的引用.如果窗口是一个框架,那么 top 属性引用包含框架的顶层窗口. ...

  4. 在Simscape中创建虚拟机器人环境(二):添加物理、对象交互和自主

    在Simscape中创建虚拟机器人环境:添加物理.对象交互和自主 由阿列克莎·桑切斯, (2020年8月31日),转载博客地址:https://blogs.mathworks.com/racing-l ...

  5. NVisionXR_iOS教程六 —— 场景中对象交互

    本章节将介绍如何与场景中的对象进行交互,接着上一章节的代码,我们往立方体对象  添加如下代码,并实现它的代理<HitEventDelegate>  代码: // 创建一个立方体 NVBox ...

  6. (转)Hibernate框架基础——在Hibernate中java对象的状态

    http://blog.csdn.net/yerenyuan_pku/article/details/52760627 在Hibernate中java对象的状态 Hibernate把对象分为4种状态: ...

  7. WWDC 2013 Session笔记 - iOS7中的多任务

    这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览.本文仅作为个人记录使用,也欢迎在许可协议范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作.如果您觉得本站对您能有帮助, ...

  8. iOS7 中的新特性

    iOS7 中的新特性 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳 ...

  9. TensorFlow中的对象检测教程:实时对象检测

    导言 创建能够在单个图像中识别和定位多个对象的精确机器学习模型仍然是计算机视觉中的核心挑战.但随着最近深度学习的进步,对象检测应用程序比以往更容易开发.TensorFlow的对象检测API是一个构建与 ...

最新文章

  1. Mac上搭建Nginx + rtmp
  2. 诺贝尔物理学奖得主Arthur Ashkin去世,他发明了“激光镊子”,曾抱怨被诺奖遗忘...
  3. 编程入门python语言是多大孩子学的-如何看待将Python作为少儿编程的基础语言?...
  4. SQL链表查询 数据库为空
  5. Spring Cloud Gateway(路由)
  6. word 公式编号 右侧对齐_word排版实例:如何将文档中的公式与文字对齐
  7. 计算机网络:05---网络类型:局域网、城域网、广域网、个域网、无线网络
  8. 1 计算机网络体系结构与OSI参考模型
  9. JAVASCRIPT处理返回的XML字符串
  10. js添加事件 attachEvent 和addEventListener的用法
  11. Android 隐藏、显示软键盘方法
  12. 完成一个Laravel项目的过程
  13. 深度学习 | MATLAB卷积神经网络原理描述
  14. ipad无法加入网络怎么办?
  15. Android 正则表达式
  16. 画象棋棋盘c语言程序设计教程课后答案,绘制中国象棋棋盘(c语言)
  17. 为什么我的背景图片加载不出来
  18. 360极速浏览器取消默认迅雷下载的正确方法
  19. 【iOS】关于keyWindow的获取
  20. 一文读懂什么是cookie和session。

热门文章

  1. Odoo10教程---模块化三:模型约束,高级视图,工作流,安全性,向导,国际化和报表等
  2. 把配置的jenkins主路径删掉_Jenkins详细教程
  3. c语言控制led以1s速度,C语言使用定时器的方法控制LED灯以1S的速度闪亮
  4. js取thymeleaf值_26、模板引擎thymeleaf
  5. python 命令行解析函数_python命令行解析之parse_known_args()函数和parse_args()使用区别介绍...
  6. 计算机基础知识187,中职计算机基础 (187)(11页)-原创力文档
  7. ionic4 中使用 url scheme 插件 点击链接打开app
  8. php-open网站还可以用,nginx+php使用open_basedir限制站点目录防止跨站
  9. oracle 循环修改数据库,oracle对一个表的多行数据进行修改,SQL批量修改
  10. CoreException: Could not get the value for parameter compilerId for plugin execution default-compile