详解 JSExport:JavaScript 与 Objective-C 的通信桥梁

230 阅读3分钟

前言

在 iOS 开发中,JavaScriptCore 框架提供了强大的 JS 引擎,可以让我们在应用中运行 JavaScript 代码。而 JSExport 是这个框架中最重要的机制之一,它可以让我们将 Objective-C 的对象暴露给 JavaScript 调用,以使 JavaScript 可以像使用普通 JS 对象一样访问 Objective-C 的方法和属性。

我们先来了解下什么是 JSExport。

JSExport 是什么?

简单来说,JSExport 是一个协议,只要你定义的 Objective-C 协议继承自它,并让一个类遵循这个协议,就可以将该类的方法和属性导出为 JavaScript 可访问的接口

@protocol JSExport

了解完概念,来看下它在代码中是如何使用的。

基本使用步骤

使用 JSExport 通常需要以下几步:

  • 创建一个继承自 JSExport 的协议;
  • 将希望暴露的方法或属性声明在协议中;
  • 创建一个遵循该协议的 Objective-C 类;
  • 将该类的实例注入到 JSContext 中;
  • 在 JavaScript 中调用暴露出来的方法。

比如我们想把下面的代码暴露给 JS 调用,官方文档的示例代码如下:

@protocol MyPointExports <JSExport>
@property double x;
@property double y;
- (NSString *)description;
- (instancetype)initWithX:(double)x y:(double)y;
+ (MyPoint *)makePointWithX:(double)x y:(double)y;
@end
 
@interface MyPoint : NSObject <MyPointExports>
- (void)myPrivateMethod;  // This isn't in the MyPointExports protocol, so it isn't visible to JavaScript code.
@end
 
@implementation MyPoint

@end

我们来分类看一下如何将 OC 的代码暴露给 JS 调用:

暴露属性

示例代码如下:

// 在协议中声明
@protocol MyPointExports <JSExport>
@property double x;
@property double y;
@end

// 类中合成属性的 set get 方法
#import "MyPoint.h"

@implementation MyPoint

@synthesize x;
@synthesize y;

@end

// 在 JS 中调用暴露的属性
JSContext *context = [[JSContext alloc] init];

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

MyPoint *point = [MyPoint new];
point.x = 10.2;
point.y = 12.1;


context[@"point"] = point;

JSValue *xResult = [context evaluateScript:@"point.x"];
JSValue *yResult = [context evaluateScript:@"point.y"];
NSLog(@"X:%f, Y:%f", [xResult toDouble], [yResult toDouble]);

打印结果:X:10.200000, Y:12.100000。

暴露实例方法

示例代码如下:

// 在协议中声明
@protocol MyPointExports <JSExport>

- (NSString *)description;
- (instancetype)initWithX:(double)x y:(double)y;

@end
// 在类中实现
- (NSString *)description {
    return @"MyPoint";
}

- (instancetype)initWithX:(double)x y:(double)y {
    if (self = [super init]) {
        self.x = x;
        self.y = y;
    }
    return self;
}
// 在 JS 中调用暴露的方法
JSContext *context = [[JSContext alloc] init];
context[@"MyPoint"] = [MyPoint class];
JSValue *result = [context evaluateScript:@"new MyPoint(1, 2)"];
NSLog(@"X:%f, Y:%f", [result[@"x"] toDouble], [result[@"y"] toDouble]);

打印结果:X:1.000000, Y:2.000000。

暴露类方法

// 在协议中声明
@protocol MyPointExports <JSExport>

@property double x;
@property double y;

- (instancetype)initWithX:(double)x y:(double)y;

+ (MyPoint *)makePointWithX:(double)x y:(double)y;

@end
// 在类中实现

@implementation MyPoint

@synthesize x;
@synthesize y;

+ (MyPoint *)makePointWithX:(double)x y:(double)y {
    return [[MyPoint alloc] initWithX:x y:y];
}

- (instancetype)initWithX:(double)x y:(double)y {
    if (self = [super init]) {
        self.x = x;
        self.y = y;
    }
    return self;
}
@end
// 在 JS 中调用暴露的类方法
JSContext *context = [[JSContext alloc] init];
context[@"MyPoint"] = [MyPoint class];
JSValue *result = [context evaluateScript:@"MyPoint.makePointWithXY(3, 4)"];
NSLog(@"X:%f, Y:%f", [result[@"x"] toDouble], [result[@"y"] toDouble]);

打印结果:X:3.000000, Y:4.000000。

总结

JSExport 是 JavaScriptCore 框架中连接 Objective-C 与 JavaScript 的核心机制,正确的使用可以让你灵活地在原生与脚本之间切换逻辑,适用场景如下:

  • 配置型逻辑引擎;
  • 脚本化功能扩展;
  • 小程序平台;
  • 混合框架。

使用 JSExport 的关键步骤:

  • 明确协议继承 JSExport
  • 方法命名符合规则;
  • 类遵循协议;
  • 注入实例到 JSContext

通过这套机制,你可以让 JavaScript 像本地对象一样调用 Objective-C 类中的方法,极大提升了扩展性和灵活性。