5

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

这是我正在尝试做的精简版。我有一个需要引用 Javascript 对象的 ViewController,并且该 Javascript 对象需要能够调用 ViewController 上的方法。ViewController 有一个显示计数的 UILabel,并有两个 UIButton,“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?

4

2 回答 2

2

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

于 2013-12-01T23:34:07.543 回答
0

只是好奇,但是您是否尝试将 JSContext *context 和 JSValue *value 属性添加到您的类的私有接口?

我的预感是您在初始化中创建了 JSContext *context,但根本没有保留它,所以看起来您的整个 JSContext 可能会通过 ARC 以及您的 JSValue *value 在某个意外点收集起来。但这很棘手,因为您是通过 _managedValue 引用它...

我对此不是 100% 确定的,但我的猜测是您根本不需要 managedValue。在调用过程中,您没有从 Javascript 传递 JSValue,-(void)updateLabel这是 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 框架了),所以我实际上可能会走得很远!

于 2014-01-17T14:41:36.020 回答