码迷,mamicode.com
首页 > 移动开发 > 详细

ios7 JavaScriptCore.framework

时间:2016-05-12 11:52:31      阅读:218      评论:0      收藏:0      [点我收藏+]

标签:

这个框架其实只是基于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,或者是转换过去。其一一对应方式可见下表:

下面通过一些简单的Demo来演示:

1. 基本类型转换

JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];
NSLog(@"JSValue: %@,int: %d", jsVal, iVal);

结果如下:
技术分享
js变量存储到JSContext中,可以通过下边取得。

2. 数组类型

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var arr = [21, 7, ‘lile.com‘];"];
JSValue *jsArr = context[@"arr"];
NSLog(@"JS Array:%@;Length:%@", jsArr, jsArr[@"length"]);

jsArr[1] = @"blog";
jsArr[7] = @7;
NSLog(@"JS Array:%@;Length:%d", jsArr, [jsArr[@"length"] toInt32]);

NSArray *nsArr = [jsArr toArray];
NSLog(@"NSArray:%@", nsArr);

结果截图:
技术分享

通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且JSValue是遵循JavaScript的数组特性:无下标越位,自动延展数组大小。并且通过JSValue还可以获取JavaScript对象上的属性,比如例子中通过”length”就获取到了JavaScript数组的长度。在转成NSArray的时候,所有的信息也都正确转换了过去。

3. 方法的转换

各种数据类型可以转换,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});"];

结果如下:
技术分享

通过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]);

结果如下:
技术分享

JSValue还提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContext的globalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue对象调用。

4. 异常处理

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;
};

结果如下:
技术分享

5. 使用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);
    }
};

6. 键值对编程—Dictionary

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

JSContext *context = [[JSContext alloc] init];
JSValue *obj = [context evaluateScript:@"var jsObj = {number:7,name:‘lile‘};jsObj"];
NSDictionary *dic = [obj toDictionary];
NSLog(@"%@, %@", dic[@"name"], dic[@"number"]);

结果如下:
技术分享

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

JSContext *context = [[JSContext alloc] init];
        NSDictionary *dic = @{@"name":@"lile", @"age":@(23)};
        context[@"dic"] = dic;
        [context evaluateScript:@"log(dic.name, dic[‘age‘])"];

7. 语言穿梭机—JSExport协议

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

在.h文件中

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol PersonProtocol <JSExport>

@property (nonatomic, strong) NSDictionary *urls;

- (NSString *)fullName;

@end

@interface Person : NSObject <PersonProtocol>

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;

@end

在.m文件中:

#import "Person.h"

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

JSContext *context = [[JSContext alloc] init];

// initialize person object
Person *person = [[Person alloc] init];
context[@"p"] = person;
person.firstName = @"Li";
person.lastName = @"Le";
person.urls = @{@"site":@"http://blog.csdn.net/CoderLiLe"};

// 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.csdn.net/CoderLiLe‘}"];
[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);

结果如下:
技术分享

References:
http://justsee.iteye.com/blog/2036713

ios7 JavaScriptCore.framework

标签:

原文地址:http://blog.csdn.net/coderlile/article/details/51373941

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!