【问题标题】:How can I use JSManagedValue to avoid a reference cycle without the JSValue getting released?如何在不释放 JSValue 的情况下使用 JSManagedValue 来避免引用循环?
【发布时间】:2013-10-05 20:14:32
【问题描述】:

我在尝试使用 JSManagedValue 时遇到了问题。根据我基于Session 615 at WWDC 2013 的理解,当您想要从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 确认 ViewController 永远不会释放。

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?

【问题讨论】:

    标签: javascript objective-c javascriptcore


    【解决方案1】:

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

    【讨论】:

    【解决方案2】:

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

    我的预感是您在初始化时创建了 JSContext *context,但根本没有保留它,所以看起来您的整个 JSContext 可能会通过 ARC 以及您的 JSValue *value 在某个意外点收集起来。但这很棘手,因为您是通过 _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 框架了),所以我可能真的很遥远!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 2012-04-24
      • 1970-01-01
      • 1970-01-01
      • 2020-11-11
      • 1970-01-01
      相关资源
      最近更新 更多