JavaScriptCore的基本用法(二)

代理设置(JS调用OC的第二种方法)

h文件

//首先写一个协议  遵守JSExport协议
@protocol JSExportTest <JSExport>
//宏转换下,将JS函数名称指定为Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, assign) NSInteger sum;

@end


//建一个对象实现这个协议
@interface JSTest : NSObject<JSExportTest>

@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 <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
//首先写一个协议  遵守JSExport协议
@protocol JSExportTest <JSExport>
//宏转换下,将JS函数名称指定为Add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
@property (nonatomic, strong) JSValue *value;

@end


//建一个对象实现这个协议
@interface JSTest : NSObject<JSExportTest>

@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实例进行操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容