Capire la raccolta dei rifiuti in AS3

Hai mai usato un'applicazione Flash e notato un certo ritardo? Ancora non sai perché quel fantastico gioco in flash gira lentamente sul tuo computer? Se vuoi saperne di più su una possibile causa di esso, allora questo articolo è per te.

Abbiamo trovato questo fantastico autore grazie a FlashGameLicense.com, il posto dove comprare e vendere giochi Flash!

Tutorial ripubblicato

Ogni poche settimane, rivisitiamo alcuni dei post preferiti del nostro lettore da tutta la cronologia del sito. Questo tutorial è stato pubblicato per la prima volta nel giugno 2010.


Anteprima del risultato finale

Diamo un'occhiata al risultato finale su cui lavoreremo:


Passaggio 1: un riferimento rapido al processo

Prima di entrare nel vero argomento, è necessario prima conoscere un po 'di come l'istanziazione e il referenziamento funzionano in AS3. Se hai già letto su questo, consiglio comunque di leggere questo piccolo passo. In questo modo, tutta la conoscenza sarà fresca nella tua testa e non avrai problemi a leggere il resto di questo suggerimento rapido!

La creazione e il riferimento delle istanze in AS3 è diverso da quello che molti pensano. L'istanziazione (o "creazione") di qualcosa accade solo quando il codice chiede di creare un oggetto. Di solito, ciò avviene tramite la parola chiave "nuova", ma è presente anche quando si utilizza a sintassi letterale o definire i parametri per le funzioni, ad esempio. Esempi di questo sono mostrati di seguito:

 // Instantiation attraverso la "nuova" parola chiave new Object (); nuova matrice (); nuovo int (); new String (); new Boolean (); nuova data (); // Instantiation attraverso la sintassi letterale ; []; 5 "Ciao mondo!" true // Istanziazione tramite parametri di funzione private function tutExample (parameter1: int, parameter2: Boolean): void

Dopo che un oggetto è stato creato, rimarrà da solo fino a quando qualcosa lo fa riferimento. Per fare ciò, generalmente si crea una variabile e si passa il valore dell'oggetto alla variabile, in modo che sappia quale oggetto attualmente detiene. Tuttavia (e questa è la parte che la maggior parte delle persone non conosce), quando si passa il valore di una variabile a un'altra variabile, non si sta creando un nuovo oggetto. Stai invece creando un altro collegamento all'oggetto che ora entrambe le variabili contengono! Vedi l'immagine qui sotto per chiarimenti:

L'immagine assume entrambi Variabile 1 e Variabile 2 può contenere lo smiley (cioè possono avere lo stesso tipo). Nella parte sinistra, solo Variabile 1 esiste. Tuttavia, quando creiamo e impostiamo Variabile 2 allo stesso valore di Variabile 1, non stiamo creando un collegamento tra Variabile 1 e Variabile 2 (parte in alto a destra dell'immagine), invece stiamo creando un collegamento tra l'emoticon e Variabile 2 (parte in basso a destra dell'immagine).

Con questa conoscenza, possiamo passare al Garbage Collector.


Step 2: Ogni città ha bisogno di un Garbage Collector

È ovvio che ogni applicazione ha bisogno di una certa quantità di memoria da eseguire, poiché ha bisogno di variabili per contenere i valori e usarli. Ciò che non è chiaro è come l'applicazione gestisce gli oggetti che non sono più necessari. Li ricicla? Li elimina? Lascia l'oggetto in memoria fino alla chiusura dell'applicazione? Tutte e tre le opzioni possono accadere, ma qui parleremo in modo specifico del secondo e del terzo.

Immagina una situazione in cui un'applicazione crea molti oggetti quando viene inizializzata, ma una volta che questo periodo finisce, oltre la metà degli oggetti creati rimane inutilizzata. Cosa accadrebbe se fossero lasciati nella memoria? Prenderanno sicuramente molto spazio, causando ciò che la gente chiama ritardo, che è un notevole rallentamento nell'applicazione. Alla maggior parte degli utenti non piacerebbe questo, quindi dobbiamo evitarlo. Come possiamo codificare per rendere l'applicazione più efficiente? La risposta è nel Netturbino.

Il Garbage Collector è una forma di gestione della memoria. Ha lo scopo di eliminare qualsiasi oggetto che non viene utilizzato e occupa spazio nella memoria del sistema. In questo modo l'applicazione può essere eseguita con l'utilizzo della memoria mimimum. Vediamo come funziona:

Quando l'applicazione inizia a funzionare, richiede una quantità di memoria dal sistema che verrà utilizzata dall'applicazione. L'applicazione inizia quindi a riempire questa memoria con tutte le informazioni necessarie; ogni oggetto che crei entra in esso. Tuttavia, se l'utilizzo della memoria si avvicina alla memoria richiesta inizialmente, viene eseguito il Garbage Collector, alla ricerca di qualsiasi oggetto non utilizzato per svuotare parte dello spazio nella memoria. A volte questo causa un po 'di ritardo nell'applicazione, a causa del grande overhead della ricerca di oggetti.

Nell'immagine, puoi vedere il picchi di memoria (cerchiato in verde). I picchi e il calo improvviso sono causati dal garbage collector, che agisce quando l'applicazione ha raggiunto l'utilizzo di memoria richiesto (la linea rossa), rimuovendo tutti gli oggetti non necessari.


Passaggio 3: avvio del file SWF

Ora che sappiamo cosa può fare Garbage Collector per noi, è tempo di imparare come codificare per trarne tutti i benefici. Prima di tutto, dobbiamo sapere come funziona il Garbage Collector, in una prospettiva pratica. Nel codice, gli oggetti diventano idonei per Garbage Collection quando diventano irraggiungibili. Quando non è possibile accedere a un oggetto, il codice capisce che non verrà più utilizzato, quindi deve essere raccolto.

Actionscript 3 controlla la raggiungibilità tramite radici della raccolta dei rifiuti. Al momento non è possibile accedere a un oggetto attraverso una root di raccolta dei dati inutili, diventa idoneo per la raccolta. Di seguito viene visualizzato un elenco delle principali radici della raccolta dei dati inutili:

  • Variabili a livello di pacchetto e statiche.
  • Variabili locali e variabili nell'ambito di un metodo o una funzione di esecuzione.
  • Variabili di istanza dall'istanza della classe principale dell'applicazione o dall'elenco di visualizzazione.

Per capire come gli oggetti sono gestiti da Garbage Collector, dobbiamo codificare ed esaminare cosa sta accadendo nel file di esempio. Userò il progetto AS3 di FlashDevelop e il compilatore di Flex, ma suppongo che tu possa farlo su qualsiasi IDE che desideri, dal momento che non useremo elementi specifici che esistono solo in FlashDevelop. Ho creato un semplice file con un pulsante e una struttura di testo. Poiché questo non è l'obiettivo di questo suggerimento rapido, lo spiegherò rapidamente: quando si fa clic su un pulsante, si attiva una funzione. In qualsiasi momento vogliamo visualizzare del testo sullo schermo, tu chiami una funzione con il testo e viene visualizzata. C'è anche un altro campo di testo per mostrare una descrizione per i pulsanti.

L'obiettivo del nostro file di esempio è creare oggetti, eliminarli ed esaminare cosa succede loro dopo che sono stati cancellati. Avremo bisogno di un modo per sapere se l'oggetto è vivo o no, quindi aggiungeremo un listener ENTER_FRAME a ciascuno degli oggetti e farli visualizzare del testo con il tempo in cui sono stati vivi. Quindi codifichiamo il primo oggetto!

Ho creato una divertente immagine di smiley per gli oggetti, in omaggio al grande tutorial di gioco di Avoider di Michael James Williams, che utilizza anche immagini di smiley. Ogni oggetto avrà un numero sulla sua testa, quindi possiamo identificarlo. Inoltre, ho chiamato il primo oggetto TheObject1, e il secondo oggetto TheObject2, quindi sarà facile da distinguere. Andiamo al codice:

 private var _theObject1: TheObject1; funzione privata newObjectSimple1 (e: MouseEvent): void // Se esiste già un oggetto creato, non fare nulla se (_theObject1) restituisce; // Crea il nuovo oggetto, impostalo nella posizione in cui dovrebbe trovarsi e aggiungi all'elenco di visualizzazione in modo che possiamo vedere che è stato creato _theObject1 = new TheObject1 (); _theObject1.x = 320; _theObject1.y = 280; _theObject1.addEventListener (Event.ENTER_FRAME, changeTextField1); addChild (_theObject1); 

Il secondo oggetto sembra quasi lo stesso. Ecco qui:

 private var _theObject2: TheObject2; funzione privata newObjectSimple2 (e: MouseEvent): void // Se esiste già un oggetto creato, non fare nulla se (_theObject2) restituisce; // Crea il nuovo oggetto, impostalo nella posizione in cui dovrebbe trovarsi e aggiungi all'elenco di visualizzazione in modo che possiamo vedere che è stato creato _theObject2 = new TheObject2 (); _theObject2.x = 400; _theObject2.y = 280; _theObject2.addEventListener (Event.ENTER_FRAME, changeTextField2); addChild (_theObject2); 

Nel codice, newObjectSimple1 () e newObjectSimple2 () sono funzioni che vengono attivate quando si fa clic sul pulsante corrispondente. Queste funzioni creano semplicemente un oggetto e lo aggiungono sullo schermo, quindi sappiamo che è stato creato. Inoltre, crea un ENTER_FRAME ascoltatore di eventi in ogni oggetto, che gli farà visualizzare un messaggio ogni secondo, a condizione che siano attivi. Ecco le funzioni:

 funzione privata changeTextField1 (e: Event): void // Il nostro esempio è in esecuzione a 30FPS, quindi aggiungiamo 1/30 su ogni frame nel conteggio. _objectCount1 + = 0.034; // Controlla se _objectCount1 ha passato un altro secondo if (int (_objectCount1)> _secondCount1) // Visualizza un testo nella schermata displayText ("Object 1 is alive ..." + int (_objectCount1)); _secondCount1 = int (_objectCount1); 
 funzione privata changeTextField2 (e: Event): void // Il nostro esempio è in esecuzione a 30FPS, quindi aggiungiamo 1/30 su ogni frame nel conteggio. _objectCount2 + = 0.034; // Controlla se _objectCount2 ha passato un altro secondo if (int (_objectCount2)> _secondCount2) // Visualizza un testo nella schermata displayText ("Object 2 is alive ..." + int (_objectCount2)); _secondCount2 = int (_objectCount2); 

Queste funzioni mostrano semplicemente un messaggio sullo schermo con il tempo in cui gli oggetti sono stati vivi. Ecco il file SWF con l'esempio corrente:


Passaggio 4: eliminazione degli oggetti

Ora che abbiamo coperto la creazione di oggetti, proviamo qualcosa: ti sei mai chiesto cosa sarebbe successo se elimini effettivamente (rimuovi tutti i riferimenti) un oggetto? Raccoglie i rifiuti? Questo è ciò che testeremo ora. Stiamo per creare due pulsanti di cancellazione, uno per ciascun oggetto. Facciamo il codice per loro:

 funzione privata deleteObject1 (e: MouseEvent): void // Controlla se _theObject1 esiste realmente prima di rimuoverlo dall'elenco di visualizzazione se (_theObject1 && contiene (_theObject1)) removeChild (_theObject1); // Rimozione di tutti i riferimenti all'oggetto (questo è l'unico riferimento) _theObject1 = null; // Visualizza un testo nella schermata displayText ("Oggetto eliminato 1 con successo!"); 
 funzione privata deleteObject2 (e: MouseEvent): void // Controlla se _theObject2 esiste realmente prima di rimuoverlo dall'elenco di visualizzazione se (_theObject1 && contiene (_theObject2)) removeChild (_theObject2); // Rimozione di tutti i riferimenti all'oggetto (questo è l'unico riferimento) _theObject2 = null; // Visualizza un testo nella schermata displayText ("Oggetto eliminato 2 con successo!"); 

Diamo un'occhiata al SWF ora. Cosa pensi che succederà?

Come potete vedere. Se fai clic su "Crea oggetto1" e quindi su "Elimina oggetto1", non succede nulla! Possiamo dire che il codice funziona, perché il testo appare sullo schermo, ma perché l'oggetto non viene cancellato? L'oggetto è ancora lì perché non è stato effettivamente rimosso. Quando abbiamo cancellato tutti i riferimenti ad esso, abbiamo detto al codice di renderlo idoneo per la garbage collection, ma il garbage collector non viene mai eseguito. Ricordare che il garbage collector verrà eseguito solo quando l'utilizzo della memoria corrente si avvicina alla memoria richiesta quando l'applicazione ha iniziato a funzionare. Ha senso, ma come lo testeremo?

Non scriverò certamente un pezzo di codice per riempire la nostra applicazione di oggetti inutili finché l'utilizzo della memoria non diventerà troppo grande. Invece, useremo una funzione attualmente non supportata da Adobe, secondo l'articolo di Grant Skinner, che obbliga l'esecuzione del Garbage Collector. In questo modo, possiamo attivare questo semplice metodo e vedere cosa succede quando viene eseguito. Inoltre, d'ora in poi, mi riferirò a Garbage Collector come GC, per ragioni di semplicità. Ecco la funzione:

 funzione privata forceGC (e: MouseEvent): void try new LocalConnection (). connect ('pippo'); nuovo LocalConnection (). connect ('pippo');  catch (e: *)  // Visualizza un testo nella schermata displayText ("----- Garbage collection triggered-----"); 

Questa semplice funzione, che crea solo due oggetti LocalConnection (), è nota per forzare l'esecuzione del GC, quindi la chiameremo quando vogliamo che ciò accada. Non consiglio di utilizzare questa funzione in un'applicazione seria. Se lo fai per test, non ci sono problemi reali, ma se è per un'applicazione che verrà distribuita alle persone, questa non è una buona funzione da usare, poiché potrebbe incorrere in effetti negativi.

Quello che consiglio per casi come questo è che lasci che il GC funzioni al suo ritmo. Non cercare di forzarlo. Invece, concentrati sulla codifica in modo efficiente in modo che i problemi di memoria non accadano (lo copriremo al punto 6). Ora, diamo nuovamente un'occhiata al nostro SWF di esempio e fai clic sul pulsante "Raccogli spazzatura" dopo aver creato e eliminato un oggetto.

Hai provato il file? Ha funzionato! Puoi vedere che ora, dopo aver cancellato un oggetto e attivato il GC, rimuove l'oggetto! Si noti che se non si elimina l'oggetto e si chiama il GC, non accadrà nulla, poiché nel codice è ancora presente un riferimento a tale oggetto. Ora, cosa succede se proviamo a mantenere due riferimenti a un oggetto e rimuoverne uno?


Passaggio 5: creazione di un altro riferimento

Ora che abbiamo dimostrato che il GC funziona esattamente come volevamo, proviamo qualcos'altro: collega un altro riferimento a un oggetto (Object1) e rimuovi l'originale. Innanzitutto, dobbiamo creare una funzione per collegare e scollegare un riferimento al nostro oggetto. Facciamolo:

 funzione privata saveObject1 (e: MouseEvent): void // _onSave è un booleano per verificare se dovremmo collegare o scollegare il riferimento if (_onSave) // Se non ci sono oggetti da salvare, non fare nulla se (! _theObject1)  // Visualizza un testo nella schermata displayText ("Non c'è nessun oggetto 1 da salvare!"); ritorno;  // Una nuova variabile per contenere un altro riferimento a Object1 _theSavedObject = _theObject1; // Visualizza un testo nella schermata displayText ("Oggetto salvato 1 con successo!"); // Alla prossima esecuzione di questa funzione, scollegala, poiché abbiamo appena collegato _onSave = false;  else // Rimozione del riferimento ad esso _theSavedObject = null; // Visualizza un testo nella schermata displayText ("Unsaved object 1 successfully!"); // Alla prossima esecuzione di questa funzione, collegalo, poiché abbiamo appena scollegato _onSave = true; 

Se testiamo il nostro swf ora, noteremo che se creiamo Object1, quindi salvarlo, eliminarlo e forzare l'esecuzione del GC, non accadrà nulla. Questo perché ora, anche se abbiamo rimosso il collegamento "originale" all'oggetto, c'è ancora un altro riferimento ad esso, che impedisce di essere idoneo per la garbage collection. Questo è tutto ciò che devi sapere sul Garbage Collector. Non è un mistero, dopo tutto. ma come applicarlo al nostro ambiente attuale? Come possiamo usare questa conoscenza per impedire che la nostra applicazione funzioni lentamente? Questo è ciò che ci mostrerà il passaggio 6: come applicarlo in esempi reali.


Passaggio 6: Rendere efficiente il tuo codice

Ora per la parte migliore: fare in modo che il tuo codice funzioni con il GC in modo efficiente! Questo passaggio fornirà informazioni utili che dovresti conservare per tutta la vita: salvalo correttamente! Per prima cosa, vorrei introdurre un nuovo modo per costruire i tuoi oggetti nella tua applicazione. È un modo semplice, ma efficace per collaborare con il GC. In questo modo vengono introdotte due classi semplici, che possono essere estese ad altre, una volta compreso ciò che fa.

L'idea di questo modo è di implementare una funzione, chiamata destroy (), su ogni oggetto che crei e chiamarla ogni volta che finisci di lavorare con un oggetto. La funzione contiene tutto il codice necessario per rimuovere tutti i riferimenti da e verso l'oggetto (escluso il riferimento utilizzato per chiamare la funzione), in modo da assicurarsi che l'oggetto lasci la tua applicazione completamente isolata e facilmente riconoscibile dal GC. La ragione di ciò è spiegata nel prossimo passaggio. Diamo un'occhiata al codice generale per la funzione:

 // Crea questo in ogni oggetto che usi public function destroy (): void // Rimuovi i listener di eventi // Rimuovi qualsiasi cosa nella lista di visualizzazione // Cancella i riferimenti ad altri oggetti, quindi diventa completamente isolato // ... // Quando vuoi rimuovere l'oggetto, fai questo: theObject.destroy (); // E quindi null l'ultimo riferimento ad esso theObject = null;

In questa funzione, dovrai cancellare tutto dall'oggetto, in modo che rimanga isolato nell'applicazione. Dopo averlo fatto, sarà più facile per il GC localizzare e rimuovere l'oggetto. Ora esaminiamo alcune delle situazioni in cui si verificano la maggior parte degli errori di memoria:

  • Oggetti che vengono utilizzati solo in un intervallo di esecuzione: fai attenzione a questi, perché possono essere quelli che consumano molta memoria. Questi oggetti esistono solo per un certo periodo di tempo (ad esempio, per memorizzare i valori quando viene eseguita una funzione) e non vengono utilizzati molto spesso. Ricordati di rimuovere tutti i riferimenti ad essi dopo aver finito con loro, altrimenti puoi averne molti nella tua applicazione, prendendo solo spazio di memoria. Tieni presente che se crei molti riferimenti a loro, devi eliminare ognuno di essi attraverso il distruggere() funzione.
  • Oggetti rimasti nell'elenco di visualizzazione: rimuovi sempre un oggetto dall'elenco di visualizzazione se desideri eliminarlo. L'elenco di visualizzazione è uno dei radici della raccolta dei rifiuti (ricordalo?) e quindi è molto importante che ti tenga lontani i tuoi oggetti quando li rimuovi.
  • Riferimenti stage, parent e root: se ti piace usare molte di queste proprietà, ricordati di rimuoverle quando hai finito. Se molti dei tuoi oggetti hanno un riferimento a questi, potresti essere nei guai!
  • Ascoltatori di eventi: a volte il riferimento che impedisce agli oggetti di essere raccolti è un listener di eventi. Ricordati di rimuoverli o usarli come deboli ascoltatori, se necessario.
  • Array e vettori: a volte gli array e i vettori possono avere altri oggetti, lasciando all'interno di essi riferimenti di cui potresti non essere a conoscenza. Fai attenzione agli array e ai vettori!

Step 7: L'isola dei riferimenti

Sebbene lavorare con il GC sia fantastico, non è perfetto. Devi prestare attenzione a quello che stai facendo, altrimenti le cose brutte possono accadere con la tua applicazione. Vorrei dimostrare un problema che potrebbe verificarsi se non segui tutti i passaggi necessari per far funzionare correttamente il tuo codice con il GC.

A volte, se non cancelli tutti i riferimenti da e verso un oggetto, potresti avere questo problema, specialmente se colleghi molti oggetti insieme nella tua applicazione. A volte, un singolo riferimento lasciato può essere sufficiente perché ciò avvenga: tutti i tuoi oggetti formano un'isola di riferimenti, in cui tutti gli oggetti sono collegati ad altri, non permettendo al GC di rimuoverli.

Quando il GC viene eseguito, esegue due semplici operazioni per verificare la presenza di oggetti da eliminare. Uno di questi compiti è contare quanti riferimenti ha ogni oggetto. Tutti gli oggetti con 0 riferimenti vengono raccolti allo stesso tempo. L'altro compito è controllare se ci sono piccoli gruppi di oggetti che si collegano l'un l'altro, ma non è possibile accedervi, sprecando così memoria. Controlla l'immagine:

Come puoi vedere, gli oggetti verdi non possono essere raggiunti, ma il loro conteggio di riferimento è 1. Il GC esegue la seconda attività per verificare questo blocco di oggetti e rimuoverli tutti. Tuttavia, quando il blocco è troppo grande, il GC "rinuncia" al controllo e assume che gli oggetti possano essere raggiunti. Ora immagina se hai qualcosa del genere:

Questa è l'isola dei riferimenti. Ci sarebbe voluta molta memoria dal sistema e non sarebbe stata raccolta dal GC a causa della sua complessità. Sembra piuttosto male, eh? Può essere facilmente evitato, però. Assicurati di aver cancellato ogni riferimento ae da un oggetto, e quindi cose così spaventose non accadranno!


Conclusione

Questo è per ora. In questo suggerimento rapido abbiamo imparato che possiamo rendere il nostro codice migliore e più efficiente al fine di ridurre i problemi di lag e di memoria, rendendolo quindi più stabile. Per fare questo, dobbiamo capire come funzionano gli oggetti di riferimento in AS3 e come trarne vantaggio per far funzionare correttamente il GC nella nostra applicazione. Nonostante possiamo migliorare la nostra applicazione, dobbiamo fare attenzione quando lo facciamo, altrimenti può diventare anche più caotico e lento!

Spero che questo semplice suggerimento ti sia piaciuto. Se hai qualche domanda, lascia un commento qui sotto!