背景
在现在大前端的概念越来越重要的背景下,在开发 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