如何在没有JSValue发布的情况下使用JSManagedValue来避免引用循环?

时间:2013-10-05 20:14:32

标签: javascript objective-c javascriptcore

我在尝试使用JSManagedValue时遇到了问题。根据我对Session 615 at WWDC 2013的理解,当你想要从Objective-C到Javascript的引用时,反之亦然,你需要使用JSManagedValue而不是仅仅在Objective-C中存储JSValue以避免引用周期。

这是我正在尝试做的精简版。我有一个ViewController,它需要一个Javascript对象的引用,并且Javascript对象需要能够在ViewController上调用一个方法。 ViewController有一个UILabel,它显示一个计数,并有两个UIButtons,'Add'递增计数,'Reset',用一个新的ViewController创建并替换当前的视图控制器(基本上我只能验证旧的ViewController)我正在测试时正确清理。)

viewDidLoad中,ViewController调用updateLabel并能够从Javascript对象中正确获取计数。但是,在运行循环结束后,Instruments正在显示JSValue正在发布。 ViewController仍然存在,JSManagedValue也是如此,所以我认为应该防止JSValue被垃圾收集,但_managedValue.value现在返回nil。

如果我只是存储JSValue而不是使用JSManagedValue,那么它都可以直观地工作,但是ViewController和JSValue之间存在一个引用循环,正如预期的那样,并且Instruments确认ViewControllers永远不会被释放。

Javascript代码:

(function() {
  var _count = 1;
  var _view;
  return {
    add: function() {
      _count++;
      _view.updateLabel();
    },
    count: function() {
      return _count;
    },
    setView: function(view) {
      _view = view;
    }
  };
})()

CAViewController.h

@protocol CAViewExports <JSExport>
- (void)updateLabel;
@end

@interface CAViewController : UIViewController<CAViewExports>
@property (nonatomic, weak) IBOutlet UILabel *countLabel;
@end

CAViewController.m

@interface CAViewController () {
    JSManagedValue *_managedValue;
}
@end

@implementation CAViewController

- (id)init {
    if (self = [super init]) {
        JSContext *context = [[JSContext alloc] init];

        JSValue *value = [context evaluateScript:@"...JS Code Shown Above..."];
        [value[@"setView"] callWithArguments:@[self]];

        _managedValue = [JSManagedValue managedValueWithValue:value];
        [context.virtualMachine addManagedReference:_managedValue withOwner:self];
    }
    return self;
}

- (void)viewDidLoad {
    [self updateLabel];
}

- (void)updateLabel {
    JSValue *countFunc = _managedValue.value[@"count"];
    JSValue *count = [countFunc callWithArguments:@[]];
    self.countLabel.text = [count toString];
}

- (IBAction)add:(id)sender {
    JSValue *addFunc = _managedValue.value[@"add"];
    [addFunc callWithArguments:@[]];
}

- (IBAction)reset:(id)sender {
    UIApplication *app = [UIApplication sharedApplication];
    CAAppDelegate *appDelegate = app.delegate;
    CAViewController *vc = [[CAViewController alloc] init];
    appDelegate.window.rootViewController = vc;
}

处理此设置的正确方法是什么,以便在ViewController的整个生命周期中保留JSValue但不创建引用循环以便可以清理ViewController?

2 个答案:

答案 0 :(得分:2)

我也对此感到困惑。在阅读了JavaScriptCore标题以及ColorMyWords示例项目之后,对于托管值来说,为了使其值保持活动状态,所有者必须对JavaScript可见(即,分配给上下文中的值)。您可以在ColorMyWords项目的AppDelegate.m的-loadColorsPlugin方法中看到这一点。

答案 1 :(得分:0)

很好奇,但您是否尝试将JSContext *上下文和JSValue *值属性添加到您的类的私有界面?

我的预感是你在初始化中创建了JSContext *上下文,但根本没有保留它,所以看起来你的整个JSContext可能会在某些意想不到的时刻通过ARC以及你的JSValue *值收集。但它很棘手,因为你通过_managedValue引用它......

我并不是100%肯定这一点,但我的猜测是你根本不需要一个managedValue。在您致电-(void)updateLabel期间,您没有从Javascript传递JSValue,这是WWDC所述的演讲者会遇到问题的情况。您应该做的是,因为您的代码始终引用JSContext中的JSValue *值,所以要在.m中添加两个接口。

我还注意到,每次运行需要它们的ObjC方法时,您都会重新加载计数并将函数添加到JSValues中。你可以把它们作为课程的一部分,这样它们只能加载一次。

所以而不是:

@interface CAViewController () {
    JSManagedValue *_managedValue;
}
@end

应该是这样的:

@interface CAViewController ()
@property JSContext *context;
@property JSValue *value;
@property JSValue *countFunc;
@property JSValue *addFunc;
@end

你班上的其他人应该是这样的:

@implementation CAViewController

- (id)init {
    self = [super init];
    if (self) {
        _context = [[JSContext alloc] init];

        _value = [_context evaluateScript:@"...JS Code Shown Above..."];
        [_value[@"setView"] callWithArguments:@[self]];

        _addFunc = _value[@"add"];
        _countFunc = _value[@"count"];
    }
    return self;
}

- (void)viewDidLoad {
    [self updateLabel];
}

- (void)updateLabel {
    JSValue *count = [self.countFunc callWithArguments:@[]];
    self.countLabel.text = [count toString];
}

- (IBAction)add:(id)sender {
    [self.addFunc callWithArguments:@[]];
}

- (IBAction)reset:(id)sender {
    UIApplication *app = [UIApplication sharedApplication];
    CAAppDelegate *appDelegate = app.delegate;
    CAViewController *vc = [[CAViewController alloc] init];
    appDelegate.window.rootViewController = vc;
}

由于你提到你只想通过杀死ViewController来清除它,JSContext和JSValue将不再有引用,因为它们也将与ViewController一起使用(理论上是?:P)。

希望这会有所帮助。我是Objective C的新手(更不用说JavaScripCore框架了)所以我真的可以离开了!