JavaScriptCore 入门

291 阅读3分钟

背景

在现在大前端的概念越来越重要的背景下,在开发 iOS 应用时,我们常常需要在应用中执行 JavaScript 代码,或者在原生代码和 JS 之间进行交互。Apple 提供的 JavaScriptCore 框架,可以让我们在不依赖 WebView 的前提下,直接在 Objective-C 或 Swift 中嵌入 JavaScript 引擎,执行 JS 代码、传值、调用函数,从而实现双向通信。

在本篇文章中,会主要介绍 JavaScriptCore 的基本使用方式、核心类、双侧之间的互相调用以及异常处理。

首先,我们先来了解下什么是 JavaScriptCore。

JavaScriptCore 是什么?

JavaScriptCore 是 Apple 提供的一个框架,它封装了 WebKit 中的 JavaScript 引擎。通过它我们可以实现下面的功能:

  • 在 Native 应用中直接执行 JavaScript 代码;
  • 将 Native 对象或者方法暴露给 JavaScript 使用;
  • 调用 JS 函数并获取返回值;
  • 捕获处理 JS 代码中触发的异常;

了解完什么是 JavaScriptCore 以及它的使用场景,下面来看下它的核心类。

JavaScriptCore 核心类介绍

JavaScriptCore 中最常用的几个类包括以下四个:

  • JSContext:表示一个 JS 执行上下文环境(沙箱)
  • JSValue:JS 中的值在 Objective-C 中的包装
  • JSExport:通过协议导出原生方法和属性给 JS 使用
  • JSVirtualMachine:表示一个虚拟机,可用于多个 JSContext

通过这些类,我们就可以实现 Native 代码和 JS 代码之间的互相调用。

了解完概念,下面就开始写代码了。

Objective-C 调用 JavaScript 方法

示例代码如下:

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

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

JSValue *addFunc = context[@"add"];
JSValue *result = [addFunc callWithArguments:@[@5, @7]];

NSLog(@"add(5, 7) = %@", [result toNumber]); // 输出 12

首先,我们创建一个 JSContext 类型的实例对象 context 用来表示 JS 执行上下文。接着调用 evaluateScript 方法传递进去一个字符串,该字符串的内容是一个 JS 函数 add,用来计算两个参数之和。然后创建一个 JSValue 类型的对象用来接收 context 中的 add 方法。最后调用 callWithArguments 方法将需要计算的数字传递进去并将结果返回给 JSValue 类型的实例对象 result。

这就是在 Objective-C 调用 JavaScript 方法的流程。

打印结果如下:

add(5, 7) = 12

接着,我们再来看下如何在 JavaScript 调用 Objective-C 方法。

JavaScript 调用 Objective-C 方法

在 JavaScript 调用 Objective-C 方法要比在 Objective-C 调用 JavaScript 方法稍微复杂一点。需要下面三步:

  • 定义一个协议继承自 JSExport,将需要 JS 调用的方法放在协议里;
  • 声明类并实现这个协议;
  • 将类的实例对象注册给 JSContext;

示例代码如下:

// 第一步:声明协议
#import <JavaScriptCore/JavaScriptCore.h>

@protocol CalculatorExport <JSExport>

- (NSInteger)addWithNum1:(NSInteger)num1 num2:(NSInteger)num2;

@end

// 第二步:实现协议
@interface Calculator : NSObject <CalculatorExport>
@end

@implementation Calculator

- (NSInteger)addWithNum1:(NSInteger)num1 num2:(NSInteger)num2 {
    return num1 + num2;
}

@end

//将实例对象注册给 JSContext

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

Calculator *calc = [[Calculator alloc] init];
context[@"calc"] = calc;

[context evaluateScript:@"var result = calc.addWithNum1Num2(3, 4);"];

NSLog(@"结果:%@", [context[@"result"] toNumber]);

输出结果如下:

结果:7

异常处理

在两侧联调开发时,不可避免的会出现代码方面的问题,这时候我们需要通过给 exceptionHandler 赋值,在回调中处理异常的场景。

示例代码如下:

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

context.exceptionHandler = ^(JSContext *ctx, JSValue *exception) {
    NSLog(@"JS 异常:%@", exception);
};

Calculator *calc = [[Calculator alloc] init];
context[@"calc"] = calc;

[context evaluateScript:@"var result = calc.sub(3, 4);"]; // 在 Native 侧,并没有导出 sub 方法

NSLog(@"结果:%@", [context[@"result"] toNumber]);

输出结果如下:

JS 异常:TypeError: calc.sub is not a function. (In 'calc.sub(3, 4)', 'calc.sub' is undefined)
结果:nan