首页 >程序人生

JavaScriptCore 整体介绍

2018-02-10 10:40 编辑: 枣泥布丁 分类:程序人生 来源:钟颖Cyan

本文紧接着上一篇:JavaScriptCore 开发相关 - 目录 开始来介绍 JavaScriptCore 这个框架。

JavaScriptCore(之后简称 JSCore)是一个开源的框架,是 WebKit 的一部分,用最简单的话描述这个框架,它大概提供了两种能力:

  • 在原生代码里面执行 JavaScript,而不用通过浏览器

  • 把原生对象注入到 JavaScript 环境里面去

上面这两句话归纳一下就是:提供了 JS 代码与原生代码交互的能力,通过 JSCore 可以更好的进行两端的对象暴露,这使得代码可以不断地在 JS 环境和原生环境穿梭。

有一点需要注意的是,JSCore 只是实现了标准 JavaScript 语言,所以也自然就没有浏览器对象(BOM),也就是说在 JSCore 里面执行代码,是没有 window, 没有 dom, 没有 XMLHTTPRequest 这些内容的。

# JSContext

JSContext 将会是我们要用到最主要的类,一个最简单的运行 JS 的例子:

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var array = ['a', 'b', 'c']"];
NSString *string = [[context evaluateScript:[NSString stringWithFormat:@"array[%d]", 1]] toString];

这三行代码创建了一个 JS 环境:context,然后在 JS 环境里面创建了一个数组:array,然后通过 evaluateScript 取出了 array[1],所以最后 string 的值是 b。

这个用法其实很简单也很直观,最后做一个 toString 是因为 evaluate 返回的是一个 JSValue,我们需要把它转换成 Native 类型。

# JSValue

JSValue 就是跑在 JS 环境里面的对象,当你在 Native 环境使用一个 JSValue 的时候,他实际上有可能是任何类型,例如一个数字,一个字符串,一个数组甚至是一个函数,类型问题永远是最痛苦的问题,在整个 Pin 3.0 开发过程中很多时候就是在处理两端的类型问题。

实际上 Apple 代码里面有一个对应表:

Objective-C type |  JavaScript type
--------------------+---------------------
nil  |  undefined
NSNull  | null
NSString |  string
NSNumber |  number, boolean
NSDictionary |  Object object
NSArray  | Array object
NSDate  |  Date object
NSBlock (1)  |  Function object (1)
id (2)  |  Wrapper object (2)
Class (3) | Constructor object (3)

上面这个表是一个默认行为,但是在实际中我们要处理的情况会比这个要复杂的多,所以我们在之后会有专门的一篇文章来讲类型系统。

JSValue 对象带了一些 toXXX 的方法,用于将 JS 对象转换到 Native 对象,同时还有一些 isXXX 方法用于来判断是否是一个 XXX 对象。(请注意 isArray isDate 是 iOS 9 才有的,如果你需要支持更老的系统,你需要自己实现这两个函数)

# 几个细节

我给 JSValue 写了几个 Category 方法用于更好的判断数据,比如:

- (BOOL)isNilObject {
    return self.isNull || self.isUndefined;
}

另外当你在使用 toString 方法时,你永远不会得到一个 null 对象,对于 JS 里面的空字符串 toString 之后你会在 Native 得到 "undefined" 这个字符串,所以:

- (NSString *)toStringOrNil {
    return self.isNilObject ? nil : self.toString;
}

这样你能确保自己该得到 nil 的时候就得到 nil,类似的还有 date。

# 下标操作

JSContext 和 JSValue 都支持下标操作,所以我们可以轻易地通过下标操作来取到上面的对象,比如上述代码中我们分配在 context 上面的 array:

NSArray *array = [context[@"array"] toArray];

这样就将 JS 对象转换成了 Native 对象。

此外下标操作是一个读写操作,类似的还可以将 Native 对象注入到 JS 环境里:

context[@"array"] = @[@"A", @"B", @"C"];

得益于这个便利的操作,在大部分时候我们用 JS 对象和 Native 对象可以看起来一样。

# 对象枚举

当一个 JSValue 是 Array 或 Map 时,你可能需要枚举这个对象,这是很常见的需求,遗憾的是貌似 JSCore 并没有提供类似的方法,所以还是将其扩展到 Category 里面去:

typedef void (^JSValueForEachBlock) (NSInteger idx, JSValue *value);
typedef void (^JSValueForKeyValueBlock) (NSString *key, JSValue *value);

- (NSInteger)count {
    for (NSInteger idx=0; ; ++idx) {
        JSValue *value = self[idx];
        if (value == nil || value.isNilObject) {
            return idx;
        }
    }
    return 0;
}

- (void)forEach:(JSValueForEachBlock)block {
    for (NSInteger idx=0; idx

# 回调

通过 JS 代码调用 Native 代码,Native 进行一大波操作之后得到了一个结果,将这个结果回调给 JS 的方法,就是一个完整的交互过程。

上面其实提到过,JSValue 可能是一个函数,也就是说 JSContext 或者 JSValue 里面包含的 JSValue,可能是一个函数(上面这句话有点绕),那么假设我们在 JSContext 里面分配了这样一个函数:

[context evaluateScript:@"function add(a, b) { return a + b; }"];

我们直接通过下标在 Native 里面获得这个函数:

JSValue *func = context[@"add"];

并且通过 callWithArguments 来调用他:

JSValue *result = [func callWithArguments:@[@(1), @(2)]];

上述 result 是一个封装的数字 3,于是这就完成了 JS 和 Native 调来调去的过程。

上面这个例子是整个系统里面最重要基础,他表示当我们在任何时候拿到一个 JS 环境中的函数,都可以通过这个方法完成回调。

# 一个易错的细节

callWithArguments 的参数是一个 NSArray,这个参数在 JS 环境里面其实是会被转换成一个参数序列,正如上述例子中的 1 和 2 是分别被分配到了 add(a, b) 中的 a 和 b 里面去(而不是 add(array))。

所以当你真的要给 JS 环境传递一个数组时,也一定要通过 @[] 来把它变成一个二位数组。

否则的话这个数组会被分配成 (arg1, arg2, arg3 ...),当然,简化这些问题最好的版本还是用 Category 封装一下。

# Block

在 JSContext 上面我们除了给他注入 Native 对象,也可以直接注入一个 Block:

context[@"log"] = ^(NSString *string) {
    NSLog(@"%@", string);
};

这种注入本质上是直接给 JS 环境增加函数,直接调用 Native 的代码,例如上述代码你可以在 JS 环境中使用 log("") 来输出内容。

这个东西也有很多细节问题,我将会在之后讲类型系统的章节里面具体描述。

# JSExport

JSExport 是整个 JSCore 里面最神奇的部分,也正是 JSExport 让我们把 Native 对象暴露给 JS 环境异常的容易,简单说,实现 JSExport 协议,可以把 Native 对象的属性和方法暴露给 JS 环境,例如:

@protocol NativeObjectExport @property (nonatomic, assign) BOOL property1;

- (void)method1:(JSValue *)arguments;

@end

@interface NativeObject : NSObject@property (nonatomic, assign) BOOL property1;
@property (nonatomic, strong) id property2;

- (void)method1:(JSValue *)arguments;

- (void)method2;

@end

上面这个 NativeObject 对象可以是任意的对象,只要他实现了上面的 NativeObjectExport,那么这个协议里面的属性和对象就可以直接被 JS 环境使用,例如上面的 property1 和 method1,这丝毫不影响他作为一个对象可以做的其他事情。

我们只要在 context 里面注入这么一个对象,就可以在 JS 环境放肆的与 Native 进行交互了:

context[@"helper"] = [NativeObject new];

JS 环境中可以使用这些方法了:

var prop = helper.property1;
helper.method1({
  handler: function(object) {
  }
});

上面的 handler 可以拿来做什么?

当 Native 代码执行完 method1 之后,可以通过这个 handler 回调到 JS 环境:

[arguments[@"handler"] callWithArguments:@[object]];

JS 环境通过 function 的 object 拿到返回结果,这就是一个完整的流程。

# JSExportAs

这个东西我会讲一下原理但我只会在极少数的情况下用到这个东西,简单说这是一个宏,用于把 Objective-C 风格的函数起一个 JS 风格的别名:

JSExportAs(show, - (void)showMessage:(NSString *)message subtitle:(NSString *)subtitle);

这样我们在 Native 代码里面还是使用 Objective-C 风格的每个参数都显示参数名的形式,但是在 JS 代码里面的调用却成了:

show(message, subtitle);

感兴趣可以看一下这个宏是如何实现的。

我刚刚说我基本不会用到这个方法,是因为这样去实现接口并不灵活,比如说你实现一个 HTTP 接口,参数可以是非常多变的,今天有可能 5 个参数明天有可能支持 6 个,在这样的假定下,我会把所有的参数传递都通过 Dictionary 来完成,比如这样:

- (void)show:(JSValue *)arguments {
    NSString *message = [arguments[@"message"] toString];
    NSString *subtitle = [arguments[@"subtitle"] toString];
    JSValue *handler = arguments[@"handler"];
}

在 JS 端调用时:

show({
  message: "hello",
  subtitle: "world",
  handler: function() {
  }
})

这样的话可以更方便的增减参数,同时也可以更好的处理回调,这种 API 风格会贯穿整个 Pin 3.0 的开发。

# class_addProtocol

这是 Objective-C Runtime 的一个方法,他可以给一个类动态的添加一个 Protocol,这是非常重要的一个特性,上述例子中的 NativeObject 是我们新建的一个类,所以我们当然可以让他实现 JSExport 来导出我们要的属性,而通过 class_addProtocol 我们可以对已有的类导出属性,例如在之后的例子中我们将会构建 UI 系统,JS 环境里面会到处跑 UIView 对象,而 UIView 对象的各种属性,都是可以在 JS 环境里面直接访问的。

这个方法的用法十分简单,构建一个实现了 JSExport 的 Protocol:

@protocol UIViewExport @property (nonatomic, assign) CGFloat alpha;

@end

然后给 UIView 添加这个 Protocol 即可:

class_addProtocol(UIView.class, @protocol(UIViewExport));

当我们在 JS 环境里面拿到一个 UIView 时,他的 alpha 属性我们可以直接拿到了,这将省掉很多类型处理上的麻烦。

# 结语

一篇以基础为主的文章再写下去就太长了,在之后的文章里面再深入讨论数据类型、UI 系统等内容,本篇就此打住。

PS: 广告时间,看 Pin 3.0 的 JS 系统能做什么,请访问:xTeko,下载 Pin 进入扩展商店查看目前支持的 JS 扩展,关注 xTeko GitHub 查看最新的源码。

PS2: 由于知乎的代码编辑器实在太烂,上述内容基本靠手打,只保证原理正确。

- EOF -

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
我要投稿   收藏文章
上一篇:如何向外行解释频繁更改需求会令程序员炸毛?
下一篇:Git知识总览(五) Git中的merge、rebase、cherry-pick以及交互式rebase
我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部