MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C与JavaScriptCore交互技术解析

2023-05-241.6k 阅读

一、JavaScriptCore 框架概述

JavaScriptCore 是苹果公司提供的一个用于在 iOS 和 macOS 应用中执行 JavaScript 代码的框架。它基于 WebKit 引擎,使得开发者能够在原生应用中嵌入 JavaScript 脚本,实现动态功能扩展、界面交互增强等。JavaScriptCore 框架提供了一系列的类和方法,用于创建 JavaScript 上下文、执行脚本、传递数据以及处理回调等操作。

在 Objective - C 开发中引入 JavaScriptCore 框架,能够为应用带来诸多好处。例如,对于一些频繁变动的业务逻辑,可以通过更新 JavaScript 脚本而无需重新发布原生应用来实现功能变更,提高了应用的灵活性和可维护性。同时,JavaScript 作为一种广泛应用于前端开发的语言,许多前端开发者可以利用其技能在原生应用中发挥作用,促进了跨端开发的融合。

二、Objective - C 与 JavaScriptCore 交互基础

  1. 引入 JavaScriptCore 框架 在 Xcode 项目中,首先需要在项目设置的“General” -> “Frameworks, Libraries, and Embedded Content”中添加 JavaScriptCore.framework。然后在需要使用的源文件中导入头文件:
#import <JavaScriptCore/JavaScriptCore.h>
  1. 创建 JavaScript 上下文 JavaScript 上下文是执行 JavaScript 代码的环境,每个上下文都有自己独立的全局对象。在 Objective - C 中可以通过 JSContext 类来创建和管理上下文。示例代码如下:
JSContext *context = [[JSContext alloc] init];
  1. 执行 JavaScript 代码 创建好上下文后,就可以使用 evaluateScript: 方法来执行 JavaScript 代码。例如,执行一段简单的 JavaScript 代码来计算两个数的和:
NSString *jsCode = @"function add(a, b) { return a + b; } add(3, 5);";
id result = [context evaluateScript:jsCode];
NSLog(@"计算结果: %@", result);

在上述代码中,先定义了一个 JavaScript 函数 add,然后调用该函数并传递参数 35,最后通过 evaluateScript: 方法执行这段代码并获取返回结果。

三、数据传递:Objective - C 到 JavaScript

  1. 传递基本数据类型 Objective - C 中的基本数据类型(如 NSNumberNSStringNSArrayNSDictionary 等)可以直接传递给 JavaScript。例如,传递一个 NSNumber 类型的数字:
NSNumber *number = @10;
context[@"objcNumber"] = number;
NSString *jsCode2 = @"var result = objcNumber * 2; result;";
id result2 = [context evaluateScript:jsCode2];
NSLog(@"传递数字计算结果: %@", result2);

在这段代码中,先将 NSNumber 对象赋值给 JavaScript 上下文中的变量 objcNumber,然后在 JavaScript 代码中使用该变量进行计算并返回结果。

  1. 传递对象 对于自定义的 Objective - C 对象,需要先将其转换为符合 JavaScript 数据结构的形式。例如,假设有一个简单的 Person 类:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
@end

要将 Person 对象传递给 JavaScript,可以将其属性转换为 NSDictionary

Person *person = [[Person alloc] init];
person.name = @"John";
person.age = 25;
NSDictionary *personDict = @{@"name": person.name, @"age": @(person.age)};
context[@"objcPerson"] = personDict;
NSString *jsCode3 = @"var p = objcPerson; console.log('Name: ', p.name, 'Age: ', p.age); p.name;";
id result3 = [context evaluateScript:jsCode3];
NSLog(@"传递对象属性结果: %@", result3);

这里将 Person 对象的属性封装到 NSDictionary 中,然后传递给 JavaScript 上下文,在 JavaScript 中可以通过该字典访问对象的属性。

四、数据传递:JavaScript 到 Objective - C

  1. 获取 JavaScript 基本类型数据 从 JavaScript 中获取基本类型数据(如数字、字符串等)相对简单。例如,JavaScript 返回一个字符串:
NSString *jsCode4 = @"function getString() { return 'Hello from JS'; } getString();";
id result4 = [context evaluateScript:jsCode4];
if ([result4 isKindOfClass:[NSString class]]) {
    NSString *jsString = (NSString *)result4;
    NSLog(@"从 JS 获取的字符串: %@", jsString);
}

在上述代码中,执行 JavaScript 函数 getString 返回一个字符串,然后在 Objective - C 中判断返回结果的类型并进行处理。

  1. 获取 JavaScript 对象数据 当 JavaScript 返回一个对象时,在 Objective - C 中会以 JSValue 类型接收。可以通过 JSValue 的方法将其转换为 Objective - C 中的数据结构。例如,JavaScript 返回一个包含属性的对象:
NSString *jsCode5 = @"function getObject() { return {name: 'Alice', age: 30}; } getObject();";
JSValue *jsObjectValue = [context evaluateScript:jsCode5];
NSDictionary *jsObjectDict = [jsObjectValue toDictionary];
NSLog(@"从 JS 获取的对象字典: %@", jsObjectDict);

这里通过 toDictionary 方法将 JSValue 类型的 JavaScript 对象转换为 NSDictionary,方便在 Objective - C 中使用。

五、函数调用:Objective - C 调用 JavaScript 函数

  1. 调用无参数 JavaScript 函数 在 Objective - C 中调用 JavaScript 定义的无参数函数非常直接。例如,JavaScript 定义一个简单的无参数函数返回问候语:
NSString *jsCode6 = @"function sayHello() { return 'Hello, Objective - C!'; }";
[context evaluateScript:jsCode6];
JSValue *sayHelloFunction = context[@"sayHello"];
id result6 = [sayHelloFunction callWithArguments:nil];
NSLog(@"调用无参数 JS 函数结果: %@", result6);

首先在 JavaScript 中定义了 sayHello 函数,然后在 Objective - C 中获取该函数并通过 callWithArguments:nil 方法调用,因为该函数无参数,所以传递 nil

  1. 调用带参数 JavaScript 函数 对于带参数的 JavaScript 函数,在调用时需要传递相应的参数。例如,JavaScript 定义一个计算两个数乘积的函数:
NSString *jsCode7 = @"function multiply(a, b) { return a * b; }";
[context evaluateScript:jsCode7];
JSValue *multiplyFunction = context[@"multiply"];
NSNumber *param1 = @5;
NSNumber *param2 = @3;
id result7 = [multiplyFunction callWithArguments:@[param1, param2]];
NSLog(@"调用带参数 JS 函数结果: %@", result7);

这里定义了 multiply 函数,然后在 Objective - C 中获取该函数并通过 callWithArguments: 方法传递两个 NSNumber 参数进行调用。

六、函数调用:JavaScript 调用 Objective - C 函数

  1. 定义 Objective - C 函数并暴露给 JavaScript 要让 JavaScript 能够调用 Objective - C 函数,需要将 Objective - C 函数包装成 JavaScript 可调用的形式。可以通过创建一个遵循 JSExport 协议的协议,并将实现该协议的对象设置为 JavaScript 上下文的 globalObject 的属性。例如,定义一个简单的 Objective - C 函数用于计算两个数的和:
@protocol MathFunctionsExport <JSExport>
- (NSNumber *)add:(NSNumber *)a :(NSNumber *)b;
@end

@interface MathFunctions : NSObject <MathFunctionsExport>
@end

@implementation MathFunctions
- (NSNumber *)add:(NSNumber *)a :(NSNumber *)b {
    NSInteger sum = a.integerValue + b.integerValue;
    return @(sum);
}
@end

然后将 MathFunctions 对象暴露给 JavaScript:

MathFunctions *mathFunctions = [[MathFunctions alloc] init];
context[@"math"] = mathFunctions;
NSString *jsCode8 = @"var result = math.add(2, 3); result;";
id result8 = [context evaluateScript:jsCode8];
NSLog(@"JS 调用 OC 函数结果: %@", result8);

在上述代码中,先定义了 MathFunctionsExport 协议和实现该协议的 MathFunctions 类,然后将 MathFunctions 对象赋值给 JavaScript 上下文中的 math 属性,这样在 JavaScript 中就可以通过 math.add 调用 Objective - C 的 add 函数。

  1. 处理 JavaScript 回调 在一些场景下,Objective - C 函数可能需要接受 JavaScript 传递的回调函数。例如,模拟一个异步操作并在完成后调用 JavaScript 回调:
@protocol AsyncOperationsExport <JSExport>
- (void)asyncOperationWithCallback:(JSValue *)callback;
@end

@interface AsyncOperations : NSObject <AsyncOperationsExport>
@end

@implementation AsyncOperations
- (void)asyncOperationWithCallback:(JSValue *)callback {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSNumber *result = @42;
        [callback callWithArguments:@[result]];
    });
}
@end

AsyncOperations 对象暴露给 JavaScript:

AsyncOperations *asyncOps = [[AsyncOperations alloc] init];
context[@"async"] = asyncOps;
NSString *jsCode9 = @"async.asyncOperationWithCallback(function(result) { console.log('Async result: ', result); });";
[context evaluateScript:jsCode9];

在这个例子中,asyncOperationWithCallback 方法接受一个 JavaScript 回调函数 callback,在异步操作完成后调用该回调并传递结果。

七、错误处理

  1. JavaScript 代码执行错误 当执行 JavaScript 代码出现错误时,evaluateScript: 方法会返回 nil,并且 JSContextexception 属性会包含错误信息。例如:
NSString *jsCode10 = @"function divide(a, b) { return a / b; } divide(10, 0);";
id result10 = [context evaluateScript:jsCode10];
if (!result10 && context.exception) {
    NSLog(@"JS 执行错误: %@", context.exception);
}

在上述代码中,由于 JavaScript 代码中进行了除以零的操作,会导致错误,通过检查 result10 是否为 nil 以及 context.exception 是否有值来捕获并处理错误。

  1. 函数调用错误 在调用 JavaScript 函数或 Objective - C 暴露给 JavaScript 的函数时,如果参数类型不匹配等原因也可能导致错误。对于 JavaScript 调用 Objective - C 函数的错误,可以通过在协议方法中进行参数校验等方式处理。例如,在 MathFunctionsadd 方法中添加参数类型检查:
- (NSNumber *)add:(NSNumber *)a :(NSNumber *)b {
    if (![a isKindOfClass:[NSNumber class]] || ![b isKindOfClass:[NSNumber class]]) {
        NSLog(@"参数类型错误");
        return nil;
    }
    NSInteger sum = a.integerValue + b.integerValue;
    return @(sum);
}

这样当 JavaScript 传递的参数类型不正确时,能够进行相应的错误处理。

八、性能优化

  1. 减少上下文切换开销 频繁地在 Objective - C 和 JavaScript 之间切换上下文会带来一定的性能开销。尽量将相关的操作集中在一方进行,避免不必要的上下文切换。例如,如果有一系列的 JavaScript 计算操作,可以将这些操作封装在一个 JavaScript 函数中一次性执行,而不是多次执行小段的 JavaScript 代码。

  2. 优化数据传递 在传递大量数据时,注意数据结构的选择和转换方式。例如,避免在 Objective - C 和 JavaScript 之间频繁转换复杂的嵌套数据结构。如果可能,尽量在一方处理数据,减少数据传递的频率和数据量。

  3. 缓存 JavaScript 函数 对于经常调用的 JavaScript 函数,可以在 Objective - C 中缓存 JSValue 类型的函数对象,避免每次调用都重新获取。例如:

JSValue *cachedMultiplyFunction = context[@"multiply"];
for (int i = 0; i < 1000; i++) {
    id result = [cachedMultiplyFunction callWithArguments:@[@(i), @(i + 1)]];
    // 处理结果
}

通过缓存函数对象,可以提高调用性能。

九、实际应用场景

  1. 动态界面布局 在一些应用中,界面的布局可能需要根据不同的业务需求动态调整。可以通过 JavaScript 来描述界面布局的逻辑,Objective - C 根据 JavaScript 生成的布局信息来创建和更新界面。例如,通过 JavaScript 计算不同屏幕尺寸下的控件位置和大小,然后传递给 Objective - C 进行实际的界面渲染。

  2. 插件化开发 实现插件化架构,将一些功能模块以 JavaScript 插件的形式提供。Objective - C 作为宿主应用,负责加载和管理这些插件。插件可以通过 JavaScriptCore 与宿主应用进行交互,实现功能的动态扩展,如加载第三方的广告插件、统计插件等。

  3. 混合开发项目 在混合开发项目中,部分页面或功能使用 JavaScript 进行开发,通过 JavaScriptCore 与原生的 Objective - C 代码进行交互。例如,在一个原生应用中嵌入一个基于 JavaScript 的地图模块,通过交互实现地图的自定义功能、获取地图数据等。

十、与其他技术的对比

  1. 与 WebView 交互方式对比 使用 WebView 也可以实现 Objective - C 与 JavaScript 的交互,但与 JavaScriptCore 相比有一些区别。WebView 更适合展示完整的网页内容,它包含了一个完整的浏览器环境,资源开销相对较大。而 JavaScriptCore 更轻量级,专注于执行 JavaScript 代码和与 Objective - C 交互,适用于在原生应用中嵌入简单的 JavaScript 逻辑。在性能方面,对于简单的脚本执行,JavaScriptCore 通常更快,因为它不需要加载整个网页渲染引擎。

  2. 与其他跨端开发框架对比 与一些跨端开发框架(如 React Native、Flutter 等)相比,Objective - C 与 JavaScriptCore 的交互方式更贴近原生开发。React Native 和 Flutter 提供了一套统一的开发框架和 UI 组件库,旨在实现一次编写多端运行。而 Objective - C 与 JavaScriptCore 交互则更灵活,开发者可以根据需求精细地控制原生代码与 JavaScript 代码的结合,更适合对原生性能和定制性要求较高的项目,但开发成本相对较高,需要同时掌握 Objective - C 和 JavaScript 技术。

十一、注意事项

  1. 内存管理 在使用 JavaScriptCore 时,要注意内存管理。例如,当将 Objective - C 对象传递给 JavaScript 上下文时,要确保对象的生命周期得到正确管理。如果在 JavaScript 中持有对 Objective - C 对象的引用,而 Objective - C 端释放了该对象,可能会导致内存错误。同样,当 JavaScript 对象被 Objective - C 持有并使用后,也要注意及时释放相关资源。

  2. 兼容性问题 不同版本的 iOS 和 macOS 系统中,JavaScriptCore 的特性和行为可能会有一些差异。在开发过程中要注意测试不同系统版本下的兼容性,确保应用在各个目标系统上都能正常运行。同时,对于 JavaScript 代码的语法和特性,也要考虑不同 JavaScriptCore 版本的支持情况。

  3. 安全性 由于 JavaScript 代码可以在应用内执行,要注意安全性问题。避免直接执行来自不可信源的 JavaScript 代码,防止恶意代码注入导致数据泄露或其他安全风险。在传递数据和调用函数时,要进行严格的参数校验,防止非法输入导致应用崩溃或安全漏洞。