2013-10-05 32 views
5

我在嘗試使用JSManagedValue時遇到了問題。根據我對Session 615 at WWDC 2013的理解,當你想從Objective-C引用到Javascript時,反之亦然,你需要使用JSManagedValue,而不是隻在Objective-C中存儲JSValue以避免引用循環。如何在沒有JSValue的情況下使用JSManagedValue來避免引用週期?

這是我試圖做的簡化版本。我有一個ViewController,它需要一個JavaScript對象的引用,並且該JavaScript對象需要能夠在ViewController上調用一個方法。 ViewController有一個顯示計數的UILabel,它有兩個UIButtons,'Add'和Count',它們用來創建和替換當前視圖控制器和一個新的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就可以清理了?

回答

1

我也很困惑。在閱讀JavaScriptCore頭文件和ColorMyWords示例項目之後,似乎爲了使託管值保持其值,所有者必須對JavaScript可見(即分配給上下文中的值)。您可以在ColorMyWords項目的AppDelegate.m的-loadColorsPlugin方法中看到這一點。

+0

我認爲這是正確的。我不得不從會話615的視頻中讀取源代碼,以確認這一點:如果示例項目仍然可用,那將會很好。 – Poulsbo

+0

示例代碼當前位於:https://developer.apple.com/downloads/index.action?name=WWDC%202013 – Poulsbo

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將不再有引用b因爲他們也會與ViewController一起(在理論上? :P)。

希望這會有所幫助。我是Objective C的新手(更不用說JavaScripCore框架了),所以我實際上可以離開!

相關問題