Как я могу использовать JSManagedValue, чтобы избежать эталонного цикла без выпуска JSValue?

У меня возникают проблемы при попытке использовать JSManagedValue. Насколько я понимаю, на основе сеанса 615 на WWDC 2013, когда вы хотите получить ссылку от цели -C в Javascript и наоборот, вам нужно будет использовать JSManagedValue вместо того, чтобы просто сохранять JSValue в Objective-C, чтобы избежать цикла ссылок.

Вот урезанная версия того, что я пытаюсь сделать. У меня есть ViewController, которому нужна ссылка на объект Javascript, и этот объект Javascript должен иметь возможность вызывать метод в ViewController. ViewController имеет UILabel, который отображает счетчик, и имеет две кнопки UIButton: «Добавить», который увеличивает счетчик, и «Сброс», который создает и заменяет текущий контроллер представления новым 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;
}

Каков правильный способ обработки этой настройки, чтобы значение JSValue сохранялось на протяжении всего жизненного цикла ViewController, но не создавалось эталонных циклов, чтобы можно было очистить ViewController?


person Jake Riesterer    schedule 05.10.2013    source источник


Ответы (2)


Меня это тоже смутило. После прочтения заголовков JavaScriptCore, а также примера проекта ColorMyWords выясняется, что для того, чтобы управляемое значение сохраняло свое значение, владелец должен быть видимым для JavaScript (т. е. назначенным значению в контексте). Вы можете увидеть, как это описано в методе -loadColorsPlugin AppDelegate.m в проекте ColorMyWords.

person Community    schedule 01.12.2013
comment
Я думаю, это правильно. Я вынужден прочитать исходный код из видео для сеанса 615, чтобы подтвердить это: было бы неплохо, если бы пример проекта все еще был доступен. - person Poulsbo; 25.03.2015
comment
Пример кода в настоящее время находится здесь: developer.apple.com/downloads/index. action?name=WWDC%202013 - person Poulsbo; 25.03.2015

Просто любопытно, а пробовали ли вы добавить свойства JSContext *context и JSValue *value в закрытый интерфейс вашего класса?

Я предполагаю, что вы создаете JSContext *context в своей инициализации, но не сохраняете его вообще, поэтому похоже, что весь ваш JSContext может быть собран через ARC, а также ваше значение JSValue * в какой-то неожиданный момент. Но это сложно, потому что вы ссылаетесь на него через _managedValue...

Я не уверен на 100% в этом, но я предполагаю, что вам вообще не нужно manageValue. Вы не передаете JSValue из Javascript во время вызова -(void)updateLabel, что является проблемой, описанной спикером на WWDC. Что вы должны сделать вместо этого, так как ваш код повсюду ссылается на значение JSValue * из вашего JSContext, так это добавить оба в ваш интерфейс в файле .m.

Я также заметил, что вы перезагружаете счетчик и добавляете функции в JSValues ​​каждый раз, когда запускаете методы ObjC, которым они нужны. Вы можете просто оставить их как часть класса, чтобы они загружались только один раз.

Итак, вместо:

@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, у JContext и JSValue больше не будет ссылки, потому что они тоже будут работать вместе с ViewController (теоретически?: P).

Надеюсь это поможет. Я новичок в Objective C (не говоря уже о фреймворке JavaScripCore), поэтому на самом деле я могу быть далеко!

person Arjun Mehta    schedule 17.01.2014