2013-10-05 19 views
5

Ho riscontrato problemi durante il tentativo di utilizzare JSManagedValue. Dalla mia comprensione basata su Session 615 at WWDC 2013, quando si desidera avere un riferimento da Objective-C a Javascript e viceversa, sarà necessario utilizzare JSManagedValue invece di archiviare JSValue in Objective-C per evitare un ciclo di riferimento.Come posso utilizzare JSManagedValue per evitare un ciclo di riferimento senza che JSValue venga rilasciato?

Ecco una versione ridotta di ciò che sto tentando di fare. Ho un ViewController che ha bisogno di un riferimento ad un oggetto Javascript, e che l'oggetto Javascript deve essere in grado di chiamare un metodo sul ViewController. ViewController ha un UILabel che visualizza un conteggio e ha due UIButtons, 'Aggiungi' che incrementa il conteggio e 'Reset', che crea e sostituisce il controller di visualizzazione corrente con un nuovo ViewController (fondamentalmente solo per verificare il vecchio ViewController viene ripulito correttamente mentre sto testando questo).

In viewDidLoad, ViewController chiama updateLabel ed è in grado di recuperare correttamente il conteggio dall'oggetto Javascript. Tuttavia, al termine del ciclo di esecuzione, Instruments sta mostrando che JSValue viene rilasciato. Il ViewController esiste ancora, così come il suo JSManagedValue, quindi ho pensato che dovrebbe impedire a JSValue di essere raccolto in modo non corretto, ma ora _managedValue.value restituisce zero.

Se memorizzo JSValue invece di utilizzare JSManagedValue, tutto funziona visivamente, ma c'è un ciclo di riferimento tra ViewController e JSValue, come previsto, e Instruments conferma che ViewControllers non viene mai rilasciato.

codice 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; 
} 

Che cosa è il modo corretto di gestire questa configurazione in modo che JSValue venga mantenuto per tutta la durata del ViewController ma non vengono creati cicli di riferimento in modo che il ViewController possa essere ripulito?

risposta

1

Sono stato anche confuso da questo. Dopo aver letto le intestazioni JavaScriptCore e il progetto di esempio ColorMyWords, sembra che per un valore gestito mantenere il suo valore attivo, il proprietario deve essere visibile a JavaScript (ad esempio, assegnato a un valore nel contesto). Puoi vedere ciò descritto nel metodo -loadColorsPlugin di AppDelegate.m nel progetto ColorMyWords.

+0

Penso che sia corretto. Sono costretto a leggere il codice sorgente dal video per la sessione 615 per confermare questo però: Sarebbe bello se il progetto di esempio fosse ancora disponibile. – Poulsbo

+0

Il codice di esempio è attualmente disponibile: https://developer.apple.com/downloads/index.action?name=WWDC%202013 – Poulsbo

0

Solo curioso, ma hai provato ad aggiungere il contesto JSContext * e le proprietà del valore JSValue * all'interfaccia privata della classe?

La mia impressione è che si sta creando il contesto JSContext * durante l'inizializzazione, ma non lo si mantiene affatto, quindi sembra che tutto il tuo JSContext possa essere raccolto tramite ARC così come il tuo valore JSValue * in qualche punto inaspettato . Ma è complicato perché lo stai citando attraverso _managedValue ...

Non sono sicuro al 100% di questo, ma la mia ipotesi è che non dovresti avere bisogno di un valore gestito. Non stai passando un JSValue da Javascript durante la tua chiamata allo -(void)updateLabel, che è lo scenario che l'oratore del WWDC ha descritto sarebbe problematico.Quello che dovresti fare invece, dato che il tuo codice fa riferimento al valore di JSValue * dal tuo JSContext, è quello di aggiungere entrambi alla tua interfaccia nel tuo .m.

Inoltre noto che si sta ricaricando il conteggio e si aggiungono funzioni in JSValue ogni volta che si eseguono i metodi ObjC che ne hanno bisogno. Puoi semplicemente tenerli come parte della classe in modo che vengano caricati solo una volta.

Così, invece di:

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

Dovrebbe essere qualcosa di simile:

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

E il resto della classe dovrebbe essere simile:

@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; 
} 

Dal momento che lei ha citato si desidera per cancellare tutto semplicemente uccidendo ViewController, JSContext e JSValue non avranno più un riferimento b perché anche loro andranno insieme al ViewController (in teoria? : P).

Spero che questo aiuti. Sono nuovo di Objective C (per non parlare del framework JavaScripCore) quindi potrei davvero essere lontano!