2011-09-06 4 views
8

Ho una funzione che accetta un'altra funzione come parametro. Qualcosa di simile a questo:AS3 che passa una funzione come parametro crea perdite di memoria

public function onHits(target : Shape, callback : Function) : void 

lo uso passando una funzione di membro come un parametro che deve essere chiamato ogni volta che il bersaglio passato colpisce qualcosa. La funzione è chiamata più volte un frame. Così è usato da fare:

//code... 
CollisionManager.onHits(myShape, onHitCB); 
//code... 

La sulla funzione colpo:

public function onHitCB(hitObject : *) : void 
{ 
    //removed all code to test this problem 
} 

Quando faccio questo, ho una perdita di memoria. Ho isolato il problema con quel metodo onHits e ho commentato tutto il resto. onHits è un metodo vuoto senza codice al suo interno, onHitCB è anche vuoto. Se commento la chiamata a onHits, non c'è perdita di memoria e se passo null anziché onHitCB non c'è perdita di memoria.

Quindi è chiaro quando passo HitCB come parametro è il problema. Quindi ho pensato che potrebbe essere perché Flash assegna un po 'di memoria per creare il puntatore Function e non lo rilascia, ma chiamo System.gc() ogni frame in modalità debug e la perdita è ancora lì. Il che significherebbe che questo è un bug nell'SDK o che non sto facendo qualcosa di giusto.

Ho trovato una soluzione strano mantenendo una variabile che indica la funzione che assegno nel costruttore mio oggetto:

private var func : Function; 

public function MyObject() 
{ 
    func = onHitCB; 
} 

e questo sarà cancellare la memoria perdita anche se ancora passare onHitCB come parametro. Ciò significherebbe che non è la funzione "getter" per ottenere onHitCB ma qualcos'altro che causa la perdita di memoria?

Sono molto confuso. Come può causare una perdita di memoria:

public function MyObject() 
{ 
} 

public function update() : void 
{ 
    CollisionManager.onHits(myShape, onHitCB);//empty function 
} 

public function onHitCB(hitObject : *) : void 
{ 
    //removed all code to test this problem 
} 

ma non questo? :

private var func : Function; 
public function MyObject() 
{ 
    func = onHitCB; 
} 

public function update() : void 
{ 
    CollisionManager.onHits(myShape, onHitCB);//empty function 
} 

public function onHitCB(hitObject : *) : void 
{ 
    //removed all code to test this problem 
} 

e c'è un modo per non avere a che fare questa soluzione?

+0

Perché non fare onHitCB un membro della classe di CollisionManager? Sembra che la tua funzione stia perdendo portata. Sull'ultima riga di onHits prova callback = null; –

+0

Provato a impostare il callback su null alla fine di onHits ma la perdita è ancora presente. Finora, mantenere un riferimento locale alla funzione è l'unica soluzione alternativa che sono riuscito a trovare. – Godfather

risposta

5

[...] metodi legati vengono creati automaticamente quando si passa un metodo come un parametro. I metodi associati assicurano che questa parola chiave faccia sempre riferimento all'oggetto o alla classe in cui è definito un metodo. Source

Sembra che la creazione di un riferimento a un metodo non stia utilizzando un getter semplice. Un nuovo oggetto di chiusura del metodo è generato. Quindi la tua ipotesi è giusta.

Mi chiedo perché i riferimenti non vengono memorizzati nella cache per ogni istanza e perché non vengono raccolti automaticamente. È meglio evitare di creare riferimenti multipli.Fare riferimento a un metodo solo una volta è esattamente ciò che farei quando dovrei usare quel metodo in più punti, quindi per la maggior parte del tempo non lo definirei una soluzione, ma una buona pratica DRY. Nel tuo esempio ha ovviamente senso, assumendo che un riferimento al metodo utilizzi un semplice getter.

+0

Vedo, questo conferma i miei sospetti su un oggetto che viene creato ogni volta che viene passata la funzione. Tuttavia non riesco ancora a capire perché mantenere un riferimento locale mentre si passa ancora la funzione (e non il riferimento locale) non creerebbe più una perdita di memoria. – Godfather

+0

Penso che venga generato solo quando il * nome del metodo * è usato direttamente (sull'assegnazione o quando viene usato come parametro). Quindi 'func = onHitCB;' genererà la chiusura del metodo solo una volta, usando 'func' come parametro non causerà la generazione di nuovo. – Kapep

+0

sì, ma non sto usando func come parametro, sto ancora usando onHitCB (se osservi attentamente i 2 esempi sopra, entrambi chiamano onits usando onHitCB) questa è la più grande storia per me. La mia migliore esplosione in questo momento sarebbe che la chiusura del metodo sia memorizzata nella cache come riferimento debole che verrebbe normalmente ripulito dal garbage collector ma non per un motivo sconosciuto. Dato che è un riferimento debole, dovrebbe essere rigenerato ad ogni chiamata, ma poiché mantengo un riferimento localmente, rimane attivo abbastanza a lungo da poter essere riutilizzato alle chiamate successive. – Godfather

0

Non sono sicuro del codice in uso nella funzione onHits, ma se non richiede altro tempo per terminare in un altro ciclo. allora vi consiglio di fare in questo modo:

static public function onHits(target : Shape) : * 
{ 
    // do what you need 

    // return the hitObject; 
    return hitObject; 
} 

e

public function update() : void 
{ 
    // parse the object direc to to the function. 
    onHitCB (CollisionManager.onHits(myShape)); 
} 

public function onHitCB(hitObject : *) : void 
{ 
    if (hitObject == null) 
     return; 

    // if it not null then do all your calculations. 
    //removed all code to test this problem 
} 
+0

Al momento, la funzione onHits non fa nulla. Ho rimosso tutto il codice da esso ma semplicemente chiamandolo passando un puntatore Function creerà una perdita di memoria. Posso evitare il problema cambiando il design del mio codice ma semplicemente il passaggio di un puntatore Function non dovrebbe causare questo problema (>. <) – Godfather

1

Per ulteriori informazioni su esattamente che cosa funziona e cosa non causare perdite di memoria quando si utilizzano le tecniche funzionali, controlla http://www.developria.com/2010/12/functional-actionscript-part-1.html. Inoltre, tieni presente che l'utilizzo di metodi statici come questo è davvero una cattiva pratica (http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/), e stai appena cominciando a incontrare i numerosi problemi causati dall'uso di questa tecnica. Sembra che tu sia abbastanza precoce nel tuo progetto che non sei completamente impegnato in questo percorso, quindi potresti voler guardare ad altri modi per programmarlo.

-1

e questo è il motivo per cui noi non facciamo questo genere di cose in programmazione OOP stile.
La soluzione migliore è eseguire correttamente e aggiungere il callback alla classe CollisionManager.
La ragione per cui può essere GCed quando si mantiene un riferimento locale è perché la funzione non perde mai l'ambito perché quella variabile contiene il riferimento.
Una volta che qualcosa perde lo scope diventa quasi impossibile farlo.

Prova questo e guarda come perdi lo scope.

private var somevar:String = 'somevar with a string'; 
public function MyObject() 
{ 
} 

public function update() : void 
{ 
    CollisionManager.onHits(myShape, onHitCB);//empty function 
} 

public function onHitCB(hitObject : *) : void 
{ 
    trace(this.somevar) // scope should be lost at this point and somevar should be null or toss an error. 
} 
+0

Non dovrebbe funzionare? Non sono sicuro di cosa intendi perdendo lo scope (i puntatori a funzione mantengono un riferimento all'istanza di cui fanno parte in modo che possano essere chiamati con il giusto argomento). Passare una funzione per parametro funziona bene e traccia "somevar con una stringa" come dovrebbe (penso?). Non vedo perché passare una funzione di callback che deve essere chiamata quando viene rilevata una collisione può essere una "cattiva progettazione", ogni oggetto può e probabilmente dovrebbe gestire le collisioni in modo diverso. Questo è usato in più casi (box2D fa questo per il suo motore di collisione credo) poiché è più flessibile. – Godfather

+0

Ok cattivo esempio. Se avrò tempo, pubblicherò una modifica per te spiegando meglio di cosa stavo parlando. –