代理设置(JS调用OC的第二种方法)
h文件
//首先写一个协议 遵守JSExport协议
@protocol JSExportTest
//宏转换下,将JS函数名称指定为Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, assign) NSInteger sum;
@end
//建一个对象实现这个协议
@interface JSTest : NSObject
@end
m文件
@implementation JSTest
@synthesize sum = _sum;
//实现协议方法
- (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];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//将obj添加到context中
scontext[@"obj"] = [][JSTest alloc]init];
//JS里面调用obj方法,并将结果赋值给obj的sum属性
[context evaluateScript:@"obj.sum = obj.add(2,3)"];
在JS中进行调用这个对象的方法,并将结果赋值sum。唯一要注意的是OC的函数命名和JS函数命名规则问题。协议中定义的是add: b:,但是JS里面方法名字是add(a,b)。可以通过JSExportAs这个宏转换成JS的函数名字。
异常处理
Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置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内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext *)currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。
内存管理
OC使用的是ARC,JS使用的是垃圾回收机制,js的引用全都是强引用,垃圾回收机制会帮他们打破这种强引用,所以JS不存在循环引用的问题。一般情况下,OC和JS对象之间内存管理都无需我们去关心。不过还是有几个注意点需要我们去留意下。
1、不要在block里面直接使用context,或者使用外部的JSValue对象。
JSContext *context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
//直接这么使用是错误的
//context.exception = exception;
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
2.OC对象不要用属性直接保存JSValue对象,因为这样太容易造成循环引用。
下面的例子:
#import
#import
//首先写一个协议 遵守JSExport协议
@protocol JSExportTest
//宏转换下,将JS函数名称指定为Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, strong) JSValue *value;
@end
//建一个对象实现这个协议
@interface JSTest : NSObject
@end
#import "JSTest.h"
@implementation JSTest
@synthesize value = _value;
//实现协议方法
-(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]];
调用JS方法,进行赋值,JS对象保留了传进来的obj,最后,JS将自己的回调callback赋值给了obj,方便obj下次回调给JS;由于JS那边保存了obj,而且obj这边也保留了JS的回调。这样就形成了循环引用。
为了打破这种强引用,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{
// 由于是回掉的关系 obj保存了JS的回掉, js也保存了obj,这样就形成了循环引用
// JSManageValue帮助我们保存了JSValue,哪里保存的js对象在js中存在。 JSMangerValue的owner在OC中也存在。
JSManagedValue *mavalue = [JSManagedValue managedValueWithValue:value];
//建立弱引用关系
[[[JSContext currentContext] virtualMachine] addManagedReference:mavalue withOwner:self];
_value = value;
}
3.不要在不同的 JSVirtualMachine 之间进行传递JS对象。
一个JSVirtualMachine可以运行多个context,由于都是在同一个堆内存和同一个垃圾回收下,所以相互之间传值是没问题的。但是如果在不同的 JSVirtualMachine传值,垃圾回收就不知道他们之间的关系了,可能会引起异常。
4.JavaScriptCore线程是安全的。
每个context运行的时候通过lock关联的JSVirtualMachine。如果要进行并发操作,可以创建多个JSVirtualMachine实例进行操作。