代理設(shè)置(JS調(diào)用OC的第二種方法)
h文件
//首先寫一個協(xié)議 遵守JSExport協(xié)議
@protocol JSExportTest <JSExport>
//宏轉(zhuǎn)換下,將JS函數(shù)名稱指定為Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, assign) NSInteger sum;
@end
//建一個對象實現(xiàn)這個協(xié)議
@interface JSTest : NSObject<JSExportTest>
@end
m文件
@implementation JSTest
@synthesize sum = _sum;
//實現(xiàn)協(xié)議方法
- (NSInteger)add:(NSInteger)a b:(NSInteger)b{
return a + b;
}
-(void)setSum:(NSInteger)sum{
NSLog(@"%ld",(long)sum);
_sum = sum;
}
@end
在viewcontroller里面
JSContext *context = [[JSContext alloc] init];
//設(shè)置異常處理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//將obj添加到context中
scontext[@"obj"] = [][JSTest alloc]init];
//JS里面調(diào)用obj方法,并將結(jié)果賦值給obj的sum屬性
[context evaluateScript:@"obj.sum = obj.add(2,3)"];
在JS中進行調(diào)用這個對象的方法,并將結(jié)果賦值sum。唯一要注意的是OC的函數(shù)命名和JS函數(shù)命名規(guī)則問題。協(xié)議中定義的是add: b:,但是JS里面方法名字是add(a,b)。可以通過JSExportAs這個宏轉(zhuǎn)換成JS的函數(shù)名字。
異常處理
Objective-C的異常會在運行時被Xcode捕獲,而在JSContext中執(zhí)行的JavaScript如果出現(xiàn)異常,只會被JSContext捕獲并存儲在exception屬性上,而不會向外拋出。時時刻刻檢查JSContext對象的exception是否不為nil顯然是不合適,更合理的方式是給JSContext對象設(shè)置exceptionHandler,它接受的是^(JSContext *context, JSValue *exceptionValue)形式的Block。其默認值就是將傳入的exceptionValue賦給傳入的context的exception屬性:
JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception = exception;
};
[context evaluateScript:@"fengzhen = 66"];
//輸出:
// ReferenceError: Can't find variable: fengzhen
無論是把Block傳給JSContext對象讓其變成JavaScript方法,還是把它賦給exceptionHandler屬性,在Block內(nèi)都不要直接使用其外部定義的JSContext對象或者JSValue,應(yīng)該將其當(dāng)做參數(shù)傳入到Block中,或者通過JSContext的類方法+ (JSContext *)currentContext;來獲得。否則會造成循環(huán)引用使得內(nèi)存無法被正確釋放。
內(nèi)存管理
OC使用的是ARC,JS使用的是垃圾回收機制,js的引用全都是強引用,垃圾回收機制會幫他們打破這種強引用,所以JS不存在循環(huán)引用的問題。一般情況下,OC和JS對象之間內(nèi)存管理都無需我們?nèi)リP(guān)心。不過還是有幾個注意點需要我們?nèi)チ粢庀隆?/p>
1、不要在block里面直接使用context,或者使用外部的JSValue對象。
JSContext *context = [[JSContext alloc] init];
//設(shè)置異常處理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
//直接這么使用是錯誤的
//context.exception = exception;
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
2.OC對象不要用屬性直接保存JSValue對象,因為這樣太容易造成循環(huán)引用。
下面的例子:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
//首先寫一個協(xié)議 遵守JSExport協(xié)議
@protocol JSExportTest <JSExport>
//宏轉(zhuǎn)換下,將JS函數(shù)名稱指定為Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, strong) JSValue *value;
@end
//建一個對象實現(xiàn)這個協(xié)議
@interface JSTest : NSObject<JSExportTest>
@end
#import "JSTest.h"
@implementation JSTest
@synthesize value = _value;
//實現(xiàn)協(xié)議方法
-(void)setValue:(JSValue *)value{
_value = value;
}
@end
viewController里面
JSContext *context = [[JSContext alloc]init];
context.exceptionHandler = ^(JSContext *j, JSValue *v){
NSLog(@"%@",j.exception);
};
[context evaluateScript:@"function callback(){return 'hello world'};function setObj(obj){this.obj = obj;obj.value = callback}"];
[context[@"setObj"] callWithArguments:@[self.testObj]];
調(diào)用JS方法,進行賦值,JS對象保留了傳進來的obj,最后,JS將自己的回調(diào)callback賦值給了obj,方便obj下次回調(diào)給JS;由于JS那邊保存了obj,而且obj這邊也保留了JS的回調(diào)。這樣就形成了循環(huán)引用。
為了打破這種強引用,apple有一個JSManagedValue 的類,官方的原話:
The JSManagedValue's JavaScript value is reachable from JavaScript
The owner of the managed reference is reachable in Objective-C. Manually adding or removing the managed reference in the JSVirtualMachine determines reachability.
JSManagedValue 幫助我們保存JSValue,里面保存的JS對象必須在JS中存在,同時 JSManagedValue 的owner在OC中也存在.因此我們把代理的m文件修改如下:
-(void)setValue:(JSValue *)value{
// 由于是回掉的關(guān)系 obj保存了JS的回掉, js也保存了obj,這樣就形成了循環(huán)引用
// JSManageValue幫助我們保存了JSValue,哪里保存的js對象在js中存在。 JSMangerValue的owner在OC中也存在。
JSManagedValue *mavalue = [JSManagedValue managedValueWithValue:value];
//建立弱引用關(guān)系
[[[JSContext currentContext] virtualMachine] addManagedReference:mavalue withOwner:self];
_value = value;
}
3.不要在不同的 JSVirtualMachine 之間進行傳遞JS對象。
一個JSVirtualMachine可以運行多個context,由于都是在同一個堆內(nèi)存和同一個垃圾回收下,所以相互之間傳值是沒問題的。但是如果在不同的 JSVirtualMachine傳值,垃圾回收就不知道他們之間的關(guān)系了,可能會引起異常。
4.JavaScriptCore線程是安全的。
每個context運行的時候通過lock關(guān)聯(lián)的JSVirtualMachine。如果要進行并發(fā)操作,可以創(chuàng)建多個JSVirtualMachine實例進行操作。