JavaScriptCore 詳解

寫(xiě)在前面

WebViewJavascriptBridge启绰、ReactNativeJSPatch 這些 JavaScriptObjective-C 交互框架都有 JavaScriptCore 的影子拾酝,所以有必要好好了解一下 JavaScriptCore

  • JavaScript:下文簡(jiǎn)稱(chēng) JS
  • Objective-C:下文簡(jiǎn)稱(chēng) OC
  • JavaScriptCore:下文簡(jiǎn)稱(chēng) JSCore

JSCore 簡(jiǎn)介

JSCore 是 JS 引擎镶摘,通常會(huì)被叫做虛擬機(jī)湿痢,專(zhuān)門(mén)設(shè)計(jì)來(lái)解釋和執(zhí)行 JS 代碼。在 WebKit 中的結(jié)構(gòu)如下。

JavaScriptCore_img01.png
  • WebKit Embedding API 是 Browser UI 與 WebPage 進(jìn)行交互的 API 接口
  • Platform API 提供與底層驅(qū)動(dòng)的交互,如網(wǎng)絡(luò),字體渲染,影音文件解碼谅辣,渲染引擎等
  • WebCore 它實(shí)現(xiàn)了對(duì)文檔的模型化熙尉,包括了 CSS, DOM, Render 等的實(shí)現(xiàn)
  • JSCore 是專(zhuān)門(mén)處理 JS 腳本的引擎

另外 Google 的 Chromium(Chorme的開(kāi)源項(xiàng)目)也是使用 WebKit 联逻。WebKit 起源于 KDE 的開(kāi)源項(xiàng)目 Konqueror 的分支,由蘋(píng)果公司用于 Safari 瀏覽器检痰。其一條分支發(fā)展成為 Chorme 的內(nèi)核包归,2013 年 Google 在此基礎(chǔ)上開(kāi)發(fā)了新的 Blink 內(nèi)核。

Snip20200609_2.png

其他 JS 解析引擎

JavaScriptCore_img03.png

JSCore 提供給 OC 的接口

JSCore 提供給 OC 的接口如下

OC 接口類(lèi) 作用
JSVirtualMachine 為 JS 的運(yùn)行提供了底層資源铅歼,虛擬機(jī)是線程安全的
JSContext 為 JS 提供運(yùn)行環(huán)境公壤,所有的 JS 在這個(gè)上下文中執(zhí)行换可,這里可以用來(lái)管理對(duì)象,添加方法
JSValue 是 JS 和 OC 之間交互的橋梁厦幅,負(fù)責(zé)兩端對(duì)象的互相轉(zhuǎn)換
JSManagedValue 可以處理內(nèi)存管理中的一些特殊情形沾鳄,它能幫助引用技術(shù)和垃圾回收這兩種內(nèi)存管理機(jī)制之間進(jìn)行正確的轉(zhuǎn)換
JSExport 實(shí)現(xiàn) JSExport 協(xié)議可以開(kāi)放 OC 類(lèi)和它們的實(shí)例方法,類(lèi)方法确憨,以及屬性給 JS 調(diào)用
C 接口類(lèi) 作用
JSBase 定義了 JavaScriptCore 接口文件
JSContextRef 主要提供 JS 執(zhí)行所需所有資源和環(huán)境
JSObjectRef 是一個(gè) JavaScript 對(duì)象,主要提供了兩部分API休弃,一部分是創(chuàng)建 JS 對(duì)象,還有一部分是給創(chuàng)建的 JS 對(duì)象添加對(duì)應(yīng)的 Callback塔猾。
JSValueRef 一個(gè) JS 值篙骡,提供用 OC 的基礎(chǔ)數(shù)據(jù)類(lèi)型來(lái)創(chuàng)建 JS 的值,或者將 JS 的值轉(zhuǎn)變?yōu)?OC 的基礎(chǔ)數(shù)據(jù)類(lèi)型
JSStringRef JavaScript 對(duì)象中字符串對(duì)象
JSStringRefCF CFString 與 JavaScript String 相互轉(zhuǎn)化

我們?cè)谥饕褂玫氖?OC 接口類(lèi)丈甸,接下來(lái)依次分析 OC 接口類(lèi)糯俗。

JSVirtualMachine

一個(gè) JSVirtualMachine 的實(shí)例就是一個(gè)完整獨(dú)立的 JS 的執(zhí)行環(huán)境,為 JS 的執(zhí)行提供底層資源老虫。

這個(gè)類(lèi)主要用來(lái)做兩件事情:

  1. 實(shí)現(xiàn)并發(fā)的 JavaScript 執(zhí)行
  2. JavaScript 和 Objective-C 橋接對(duì)象的內(nèi)存管理

提供的接口也非常簡(jiǎn)單

NS_CLASS_AVAILABLE(10_9, 7_0)
@interface JSVirtualMachine : NSObject

(instancetype)init;

// 進(jìn)行內(nèi)存管理
- (void)addManagedReference:(id)object withOwner:(id)owner;
- (void)removeManagedReference:(id)object withOwner:(id)owner;

@end

每一個(gè) JSVirtualMachine 可以包含多個(gè) JS 上下文(JSContext 對(duì)象)叶骨。同一個(gè)虛擬機(jī)下不同的上下文之間可以相互傳值(JSValue對(duì)象)。

然而祈匙,每個(gè)虛擬機(jī)都是完整且獨(dú)立的,有其獨(dú)立的堆空間和垃圾回收器(Garbage Collector )天揖,GC 無(wú)法處理別的虛擬機(jī)堆中的對(duì)象夺欲,因此你不能把一個(gè)虛擬機(jī)中創(chuàng)建的值傳給另一個(gè)虛擬機(jī)。

JSContext

一個(gè) JSContext 表示了一次 JS 的執(zhí)行環(huán)境今膊。我們可以通過(guò)創(chuàng)建一個(gè) JSContext 去調(diào)用 JS 腳本些阅,訪問(wèn)一些 JS 定義的值和函數(shù),同時(shí)也提供了讓 JS 訪問(wèn) Native 對(duì)象斑唬,方法的接口市埋。

一個(gè) JSContext 對(duì)象對(duì)應(yīng)了一個(gè)全局對(duì)象。例如 Web 瀏覽器中的 JSContext 恕刘,其全局對(duì)象就是 Window 對(duì)象缤谎。在其他環(huán)境中,全局對(duì)象也承擔(dān)了類(lèi)似的角色褐着,用來(lái)區(qū)分不同的 JavaScript Context 的作用域坷澡。

同樣我們看一下接口,也是非常精簡(jiǎn)的

JS_EXPORT API_AVAILABLE(macos(10.9), ios(7.0))
@interface JSContext : NSObject

// 初始化含蓉,可以指定一個(gè)虛擬機(jī)频敛,如果沒(méi)有指定底層默認(rèn)創(chuàng)建一個(gè)
- (instancetype)init;
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;

// 執(zhí)行 JS 腳本项郊,返回值是 JS 中最后生成的一個(gè)值,sourceURL 認(rèn)作其源碼 URL斟赚,用作標(biāo)記
- (JSValue *)evaluateScript:(NSString *)script;
- (JSValue *)evaluateScript:(NSString *)script
              withSourceURL:(NSURL *)sourceURL API_AVAILABLE(macos(10.10), ios(8.0));

// 獲取當(dāng)前執(zhí)行的 JavaScript 代碼的 context
+ (JSContext *)currentContext;

// 獲取當(dāng)前執(zhí)行的 JavaScript function
+ (JSValue *)currentCallee API_AVAILABLE(macos(10.10), ios(8.0));

// 獲取當(dāng)前執(zhí)行的 JavaScript 代碼的 this
+ (JSValue *)currentThis;

// 獲取當(dāng)前 context 回調(diào)函數(shù)的參數(shù)
+ (NSArray *)currentArguments;

// 獲取當(dāng)前 context 的全局對(duì)象
@property (readonly, strong) JSValue *globalObject;

// 用于 JavaScript 執(zhí)行異常
@property (strong) JSValue *exception;
@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);

// 獲取當(dāng)前虛擬機(jī)
@property (readonly, strong) JSVirtualMachine *virtualMachine;

// 標(biāo)記當(dāng)前 context 
@property (copy) NSString *name API_AVAILABLE(macos(10.10), ios(8.0));

@end

JS 訪問(wèn) OC

{
    JSContext *context = [[JSContext alloc] init];
    context[@"add"] = ^(NSInteger a, NSInteger b) {
        NSLog(@"a + b = %ld", a + b);
    };
    [context evaluateScript:@"add(1, 2)"];
}

OC 訪問(wèn) JS

{
    JSContext *context = [[JSContext alloc] init];
    [context evaluateScript:@"function add(a, b) {return a + b}"];
    JSValue *addFun = context[@"add"];
    JSValue *resValue = [addFun callWithArguments:@[@1, @2]];
    NSLog(@"a + b = %@", resValue);
}

JSValue

JSValue 實(shí)例是一個(gè)指向 JS 值的引用指針。我們可以使用 JSValue 類(lèi)拗军,在 OC 和 JS 的基礎(chǔ)數(shù)據(jù)類(lèi)型之間相互轉(zhuǎn)換食绿。你也可以使用這個(gè)類(lèi)去創(chuàng)建包裝了自定義類(lèi)的 Native 對(duì)象的 JS 對(duì)象,或者是那些由 Native 方法或者 Block 實(shí)現(xiàn)的 JS 函數(shù)耀销。

在 JSCore 中铲汪,JSValue 自動(dòng)做了 OC 和 JS 的類(lèi)型轉(zhuǎn)換

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

我們繼續(xù)看一下接口狰住,非常簡(jiǎn)潔

NS_CLASS_AVAILABLE(10_9, 7_0)
@interface JSValue : NSObject

@property (readonly, strong) JSContext *context;

+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context;
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
+ (JSValue *)valueWithUInt32:(uint32_t)value inContext:(JSContext *)context;
+ (JSValue *)valueWithNewObjectInContext:(JSContext *)context;
+ (JSValue *)valueWithNewArrayInContext:(JSContext *)context;
+ (JSValue *)valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context;
+ (JSValue *)valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context;
+ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *resolve, JSValue *reject))callback API_AVAILABLE(macos(10.15), ios(13.0));
+ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context API_AVAILABLE(macos(10.15), ios(13.0));
+ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context API_AVAILABLE(macos(10.15), ios(13.0));
+ (JSValue *)valueWithNewSymbolFromDescription:(NSString *)description inContext:(JSContext *)context API_AVAILABLE(macos(10.15), ios(13.0));
+ (JSValue *)valueWithNullInContext:(JSContext *)context;
+ (JSValue *)valueWithUndefinedInContext:(JSContext *)context;

- (id)toObject;
- (id)toObjectOfClass:(Class)expectedClass;
- (BOOL)toBool;
- (double)toDouble;
- (int32_t)toInt32;
- (uint32_t)toUInt32;
- (NSNumber *)toNumber;
- (NSString *)toString;
- (NSDate *)toDate;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;

@property (readonly) BOOL isUndefined;
@property (readonly) BOOL isNull;
@property (readonly) BOOL isBoolean;
@property (readonly) BOOL isNumber;
@property (readonly) BOOL isString;
@property (readonly) BOOL isObject;
@property (readonly) BOOL isArray API_AVAILABLE(macos(10.11), ios(9.0));
@property (readonly) BOOL isDate API_AVAILABLE(macos(10.11), ios(9.0));
@property (readonly) BOOL isSymbol API_AVAILABLE(macos(10.15), ios(13.0));

- (BOOL)isEqualToObject:(id)value;
- (BOOL)isEqualWithTypeCoercionToObject:(id)value;
- (BOOL)isInstanceOf:(id)value;

// 當(dāng)前 JSValue 為一個(gè)函數(shù)的時(shí)候催植,可以通過(guò)這個(gè)方法調(diào)用
- (JSValue *)callWithArguments:(NSArray *)arguments;
// 調(diào)用 JS 中的構(gòu)造函數(shù)创南,arguments 數(shù)組內(nèi)容必須是 JSValue 對(duì)象省核,以供 JS 能順利轉(zhuǎn)化
- (JSValue *)constructWithArguments:(NSArray *)arguments;
// 當(dāng)前 JSValue 對(duì)象為 JS 中的全局對(duì)象名稱(chēng)气忠,method 為全局對(duì)象的方法名稱(chēng),arguments 為參數(shù)
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;

@end

OC 的 Block 轉(zhuǎn)換

OC 層面的 Block 是可以自動(dòng)轉(zhuǎn)換為 JS 層面的函數(shù)吨娜,JS 可以直接訪問(wèn)舌菜;但是 JS 的函數(shù) OC 確不能直接訪問(wèn),而要通過(guò) callWithArguments: 方法來(lái)調(diào)用缤骨。

OC 的 id 類(lèi)型轉(zhuǎn)換

OC 的 id 類(lèi)型傳給 JS尺借,只是一個(gè)指針,是沒(méi)法訪問(wèn)其屬性和方法的虱歪,但是 JS 回傳到 OC 的時(shí)候 OC 還是可以正常訪問(wèn)的栅表。如果需要在 JS 中,訪問(wèn) OC 對(duì)象的屬性和方法可以通過(guò) JSExport 協(xié)議來(lái)實(shí)現(xiàn)萧落,下面會(huì)詳細(xì)介紹找岖。

JSExport

實(shí)現(xiàn) JSExport 協(xié)議可以開(kāi)放 OC 類(lèi)和它們的實(shí)例方法敛滋,類(lèi)方法,以及屬性給 JS 調(diào)用

我們先看一個(gè)常規(guī)例子

@protocol OC2JSObjectExport <JSExport>

@property (nonatomic, strong) NSString *name;

- (void)callName;

+ (void)helloAtOC;

@end

@interface OC2JSObject : NSObject<OC2JSObjectExport>

@end

@implementation OC2JSObject

@synthesize name = _name;

- (void)callName
{
    NSLog(@"callName:%@", self.name);
}

+ (void)helloAtOC
{
    NSLog(@"helloAtOC");
}

@end

// 調(diào)用如下
{
    OC2JSObject *ocObj = [OC2JSObject new];
    ocObj.name = @"bob";
    
    JSContext *context = [[JSContext alloc] init];
    context[@"log"] = ^(NSString *msg){
        NSLog(@"%@", msg);
    };
    context[@"ocObj"] = ocObj;
    context[@"OC2JSObject"] = OC2JSObject.class;
    
    // 訪問(wèn)屬性
    [context evaluateScript:@"log(ocObj.name)"];
    // 訪問(wèn)實(shí)例方法
    [context evaluateScript:@"ocObj.callName()"];
    // 訪問(wèn)類(lèi)方法
    [context evaluateScript:@"OC2JSObject.helloAtOC()"];
}

如果 OC 方法有多個(gè)參數(shù)的時(shí)候

@protocol OC2JSObjectExport <JSExport>

+ (void)callVal1:(NSString *)val1 val2:(NSString *)val2;

@end

@interface OC2JSObject : NSObject<OC2JSObjectExport>

@end

@implementation OC2JSObject

+ (void)callVal1:(NSString *)val1 val2:(NSString *)val2
{
    NSLog(@"val1:%@ val2:%@", val1, val2);
}

@end

// 調(diào)用如下
{
    JSContext *context = [[JSContext alloc] init];
    context[@"OC2JSObject"] = OC2JSObject.class;
    [context evaluateScript:@"OC2JSObject.callVal1Val2('a', 'b')"];
}

多個(gè)參數(shù)的時(shí)候,轉(zhuǎn)換規(guī)則成駝峰形式

  • 去掉所有的冒號(hào)
  • 所有冒號(hào)后的第一個(gè)小寫(xiě)字母都會(huì)被轉(zhuǎn)為大寫(xiě)

如果不喜歡默認(rèn)的轉(zhuǎn)換規(guī)則箕昭,也可以使用 JSExportAs 來(lái)自定義轉(zhuǎn)換,比如

@protocol OC2JSObjectExport <JSExport>

JSExportAs(callVal, + (void)callVal1:(NSString *)val1 val2:(NSString *)val2);

@end

@interface OC2JSObject : NSObject<OC2JSObjectExport>

@end

@implementation OC2JSObject

+ (void)callVal1:(NSString *)val1 val2:(NSString *)val2
{
    NSLog(@"val1:%@ val2:%@", val1, val2);
}

@end

// 調(diào)用如下
{
    JSContext *context = [[JSContext alloc] init];
    context[@"OC2JSObject"] = OC2JSObject.class;
    [context evaluateScript:@"OC2JSObject.callVal('a', 'b')"];
}

小結(jié)

  • 實(shí)現(xiàn) JSExport 協(xié)議可以開(kāi)放 OC 類(lèi)和它們的實(shí)例方法,類(lèi)方法述召,以及屬性給 JS 調(diào)用
  • 多個(gè)參數(shù)的時(shí)候蟹地,自動(dòng)按照如下規(guī)則轉(zhuǎn)換,如果不喜歡可以通過(guò) JSExportAs 自定義
    • 去掉所有的冒號(hào)
    • 所有冒號(hào)后的第一個(gè)小寫(xiě)字母都會(huì)被轉(zhuǎn)為大寫(xiě)

JSCore 多線程

我們先看看一段 OC 的多線程代碼

{
    __block NSInteger cnt = 0;
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (NSInteger i = 0; i < 1000; i++)
    {
        dispatch_group_async(group, queue, ^{
            cnt = cnt + 1;
        });
    }
    dispatch_group_notify(group, queue, ^{
        NSLog(@"cnt:%ld", cnt);
    });
}

因?yàn)樵L問(wèn) cnt 是線程不安全的夺刑,所以最后 cnt 的值遍愿,不一定是 1000

我們?cè)倏匆欢?JS 的代碼

{
    JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
    JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
    context[@"log"] = ^(NSString *msg) {
        NSLog(@"log:%@", msg);
    };
    
    [context evaluateScript:@"var cnt = 0"];
    [context evaluateScript:@"function addCnt(){cnt = cnt + 1}"];
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (NSInteger i = 0; i < 1000; i++)
    {
        dispatch_group_async(group, queue, ^{
            [context evaluateScript:@"addCnt()"];
        });
    }
    dispatch_group_notify(group, queue, ^{
        [context evaluateScript:@"log(cnt)"];
    });
}

這段代碼發(fā)現(xiàn)沼填,cnt 都是 1000,說(shuō)明線程安全岩饼。

JavaScriptCore 提供的 API 是線程安全的薛夜。

你可以在不同的線程中,創(chuàng)建 JSValue寞冯,用 JSContext 執(zhí)行 JS 語(yǔ)句简十,但是當(dāng)一個(gè)線程正在執(zhí)行 JS 語(yǔ)句時(shí)撬腾,其他線程想要使用這個(gè)正在執(zhí)行 JS 語(yǔ)句的 JSContext 所屬的 JSVirtualMachine 就必須得等待,等待前前一個(gè)線程執(zhí)行完胰默,才能使用這個(gè) JSVirtualMachine漓踢。

JSCore 內(nèi)存管理

目前 OC 使用的是 ARC,不能自動(dòng)解決循環(huán)引用的問(wèn)題奴迅,需要我們程序員手動(dòng)去解除循環(huán)挺据,但是 JS 使用的是 GC(垃圾回收機(jī)制),所有的引用都是強(qiáng)引用暇检,同時(shí)垃圾回收器可以幫我們解決循環(huán)引用的問(wèn)題块仆,JSCore 也是一樣的构蹬,一般來(lái)說(shuō)庄敛,大多數(shù)情況下不需要我們?nèi)ナ謩?dòng)的管理內(nèi)存蜜暑。

注意1:OC 或 JS 對(duì)象,只要有一端存在強(qiáng)引用隐绵,對(duì)象就不會(huì)釋放

比如:

@interface TCURLRequest : NSURLRequest

@end

@implementation TCURLRequest

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

{
    JSContext *context = [[JSContext alloc] init];
    {
        TCURLRequest *request = [TCURLRequest requestWithURL:[NSURL URLWithString:@"https://www.google.com"]];
        JSValue *value = [JSValue valueWithObject:request inContext:context];
        [context evaluateScript:@"var ocValue"];
        [context evaluateScript:@"function setValue(value){ocValue = value}"];
        JSValue *setValueFun = context[@"setValue"];
        [setValueFun callWithArguments:@[value]];
        // 在 OC 中依许,request 這之后就要釋放了
    }
    JSValue *ocValue = context[@"ocValue"];
    TCURLRequest *request = ocValue.toObject;
    NSLog(@"%@", request);
    // 在這之后才釋放的
}

注意2:循環(huán)引用缀蹄,由于 JSValueJSContext蛀醉、JSVirtualMachine 都是強(qiáng)引用

比如衅码,下面的代碼就會(huì)出現(xiàn)循環(huán)引用

{
    JSContext *context = [[JSContext alloc] init];
    JSValue *value = [JSValue valueWithInt32:10086 inContext:context];
    context[@"log"] = ^{
        NSLog(@"%@", value);
    };
    [context evaluateScript:@"log()"];
}

我們可以使用 JSManagedValue 來(lái)解決,JSManagedValue 對(duì) JSValue 采用的是弱引用

{
    JSContext *context = [[JSContext alloc] init];
    JSValue *value = [JSValue valueWithInt32:10086 inContext:context];
    JSManagedValue *mValue = [JSManagedValue managedValueWithValue:value];
    context[@"log"] = ^{
        NSLog(@"%@", mValue);
    };
    [context evaluateScript:@"log()"];
}

當(dāng)然垛玻,這里我們也可以使用 weak 來(lái)處理帚桩。有一種情況是 OC 引用了 JS 的屬性嘹黔,然后 JS 中也引用了 OC 的屬性,這個(gè)時(shí)候由于 JS 中沒(méi)有弱引用之說(shuō)醉锄,所以必須要用 JSManagedValue浙值。

比如:

@protocol QSExport <JSExport>

@property (nonatomic, strong) JSValue *jsValue;

@end

@interface QSObject : NSObject <QSExport>

@end

@implementation QSObject

@synthesize jsValue = _jsValue;

- (void)dealloc 
{
    NSLog(@"dealloc:%@", NSStringFromClass(self.class));
}

@end

@interface QSContext : JSContext

@end

@implementation QSContext

- (void)dealloc
{
    NSLog(@"dealloc:%@", NSStringFromClass(self.class));
}

@end

// 調(diào)用
{
    NSString *script = @"var arr = [1,2,3];\
                         function setObj(obj) {\
                            this.obj = obj;\
                            obj.jsValue = arr;\
                        }";
    QSContext *context = [[QSContext alloc] init];
    [context evaluateScript:script];
    
    QSObject *obj = [[QSObject alloc] init];
    [context[@"setObj"] callWithArguments:@[obj]];
}

QSObject 中的 jsValue 引用了 JS 中的 arr开呐,而且 QSObject 對(duì)象同時(shí)也被 JS 引用筐付。當(dāng)然我們可以讓 QSObject 中的 jsValue 為 weak 指針,但是由于 JS 最新在 OC 中沒(méi)有強(qiáng)引用對(duì)象沮尿,所以 weak 指針是行不通的畜疾,

比如:

@protocol QSExport <JSExport>

@property (nonatomic, weak) JSValue *jsValue;

@end

// 調(diào)用
{
    NSString *script = @"var arr = [1,2,3];\
                         function setObj(obj) {\
                            this.obj = obj;\
                            obj.jsValue = arr;\
                        }";
    QSContext *context = [[QSContext alloc] init];
    [context evaluateScript:script];
    
    QSObject *obj = [[QSObject alloc] init];
    [context[@"setObj"] callWithArguments:@[obj]];
    NSLog(@"%@", obj.jsValue); // 這里是沒(méi)有值的
}

在這種情況下印衔,我們只能使用 JSManagedValue

@protocol QSExport <JSExport>

@property (nonatomic, strong) JSValue *jsValue;

@end

@interface QSObject : NSObject <QSExport>

@property (nonatomic, strong) JSManagedValue *jsManagedValue;

@end

@implementation QSObject

@synthesize jsValue = _jsValue;

- (void)setJsValue:(JSValue *)jsValue
{
    _jsManagedValue = [JSManagedValue managedValueWithValue:jsValue];
}

- (JSValue *)jsValue
{
    return self.jsManagedValue.value;
}

- (void)dealloc
{
    NSLog(@"dealloc:%@", NSStringFromClass(self.class));
}

@end

@interface QSContext : JSContext

@end

@implementation QSContext

- (void)dealloc
{
    NSLog(@"dealloc:%@", NSStringFromClass(self.class));
}

@end

// 調(diào)用
{
    NSString *script = @"var arr = [1,2,3];\
                         function setObj(obj) {\
                            this.obj = obj;\
                            obj.jsValue = arr;\
                        }";
    QSContext *context = [[QSContext alloc] init];
    [context evaluateScript:script];
    
    QSObject *obj = [[QSObject alloc] init];
    [context[@"setObj"] callWithArguments:@[obj]];
    NSLog(@"%@", obj.jsValue); 
}

這樣就能正常調(diào)用和釋放了瞎暑。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末了赌,一起剝皮案震驚了整個(gè)濱河市玄糟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫂拴,老刑警劉巖贮喧,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箱沦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灶伊,警方通過(guò)查閱死者的電腦和手機(jī)寒跳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)童太,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)胸完,“玉大人赊窥,你說(shuō)我怎么就攤上這事狸页。” “怎么了址遇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵傲隶,是天一觀的道長(zhǎng)窃页。 經(jīng)常有香客問(wèn)我,道長(zhǎng)乒省,這世上最難降的妖魔是什么畦木? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任十籍,我火速辦了婚禮,結(jié)果婚禮上惨篱,老公的妹妹穿的比我還像新娘围俘。我一直安慰自己,他們只是感情好簿寂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布常遂。 她就那樣靜靜地躺著挽荠,像睡著了一般泊碑。 火紅的嫁衣襯著肌膚如雪毯欣。 梳的紋絲不亂的頭發(fā)上酗钞,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天砚作,我揣著相機(jī)與錄音嘹锁,去河邊找鬼。 笑死领猾,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的面粮。 我是一名探鬼主播继低,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼袁翁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了柄驻?” 一聲冷哼從身側(cè)響起年柠,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤冗恨,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后掀抹,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蓉驹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年态兴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喘垂。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡正勒,死狀恐怖傻铣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸭限,我是刑警寧澤怪蔑,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站喧枷,受9級(jí)特大地震影響弓坞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜戚扳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一族吻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧砍艾,春花似錦巍举、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剑肯。三九已至覆旭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間型将,已是汗流浹背七兜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工腕铸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铛碑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓涛菠,卻偏偏與公主長(zhǎng)得像撇吞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子迄薄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355