Oggi trattiamo l’argomento relativo alla gestione della memoria
Nell’ ultima lezione abbiamo visto a grandi linee la logica della programmazione ad oggetti. Abbiamo visto come creare un oggetto, definire delle variabili dell’ oggetto e definire dei metodi.
Le variabili sono dei “contenitori” dove vengono memorizzate delle informazioni. Oggi vedremo abbastanza in dettaglio come richiedere questo spazio ma soprattutto la teoria del retain/release/dealloc : come liberare lo spazio.
Allocazione della memoria
Vediamo il caso piu` semplice di “richiesta” di memoria, quello che riguarda le variabili di tipo int, char, double, float etc..
Quando abbiamo bisogno di memorizzare un intero utilizziamo un int o un long int.
In questo modo dichiariamo una variabile di tipo “int” di nome “n” nella quale memorizziamo il numero 5. Se avete letto la guida riguardo le varibili, che vi avevo consigliato sapete che un int occupa 4 byte, cioe` 32bit (1 byte = 8bit).
Quindi quando dichiariamo un int noi andiamo ad occupare 4byte di memoria. Ma quand’e` che questa memoria viene liberata ?
In questa funzione laMiaFunzione, una funzione esempio completamente inutile, vengono dichiarate 3 variabili di tipo int: n, a e b. Quello che avviene dal punto di vista della memoria e` questo:
1) Viene chiamata la funzione laMiaFunzione
2) Viene occupato lo spazio dalle variabili n,a,b (12 byte di memoria)
3) Viene fatto un calcolo
4) La funzione termina, liberando i 12 byte di memoria
Quando trattero` i controlli di flusso (istruzioni if/ else if/ else) parlero` di scope di variabili e quindi questo “liberare” la memoria sara` piu` chiaro.
Quindi in una situazione semplice come “laMiaFunzione” quando la funzione finisce la memoria viene liberata. Ma e` tutto cosi` semplice ? Ovviamente no!
In Objective-C noi utilizziamo soprattutto oggetti. Noi accediamo agli oggetti tramite delle variabili dette puntatori.
La variabile “laMiaStringa” e` un puntatore. All’ interno di questa variabile c’e` scritto l’ indirizzo di memoria per accedere all’ oggetto NSString che contiene la stringa “Hello World”.
Questo grafico dovrebbe farvi capire la differenza tra puntatore ed oggetto.
Considerando il puntatore pluto, non e` che in pluto e` contenuto l’ oggetto NSString con la stringa @”Pluto”. Nella variabile pluto e` memorizzato l’ indirizzo di memoria dov’e` allocato l’ oggetto NSString. Il puntatore punta ad una zona di memoria. Quindi il puntatore pluto ci permette di accedere alla zona di memoria dov’e` contenuta l’ informazione che ci interessa.
Capire questo e` fondamentale perche` in questo modo si capisce che se non si deallocano gli oggetti, la memoria rimane occupata senza riuscire piu` a recuperarla: memory leak (http://it.wikipedia.org/wiki/Memory_leak)
Vediamo un po` in che cosa consiste un memory leak
La funzione laMiaFunzione viene chiamata e non inizialmente non ci sono puntatori dichiarati e neanche oggetti allocati.
Scrivendo NSString *pippo; dichiariamo un puntatore che ci permette di puntare ad un oggetto NSString (Il puntatore pippo verra` perso alla chiusura della funzione).
Dichiaro un altro puntatore: pluto.
Ora allochiamo un oggetto NSString che contiene la stringa “Pippo” e lo associamo al puntatore NSString *pippo.
La memoria per il nuovo oggetto viene allocata e il puntatore pippo contiene l’ indirizzo di memoria per accedere all’ oggetto NSString.
Alloco un altro oggetto NSString.
[[NSString alloc] initWithString:@”Pluto”] equivale a scrivere pluto = @”Pluto”;
Nell’ ultima riga e` esplicita l’ allocazione di memoria e l’ inizializzazione dell’ oggetto con la stringa @”Pluto”.
Ora se chiudessimo la stringa avremmo un bel memory leak. La memoria persa coincide con quella che non potremo piu` utilizzare.
Questo avviene perche` quando la funzione termina, i puntatori vengono persi e quindi non e` piu` possibile accedere agli oggetti perche` non c’e` piu` nessuna variabile (nessun puntatore) che contenga i vari indirizzi di memoria per accedere agli oggetti.
Metodi RETAIN, RELEASE e RetainCount.
Nel caso precedente se vogliamo deallocare (liberare) la memoria occupata dall’ oggetto prima che la funzione ritorni (si concluda), dobbiamo dire all’ oggetto di liberare la memoria. Questo si fa con il comando release. In realta` fra poco vedremo che non e` detto che la memoria venga subito liberata.
In questo caso scrivendo [pippo release]; deallocheremo l’ oggetto liberando la memoria.
Il motivo per cui prima dicevo che con il metodo release l’ oggetto venga deallocato e` perche` il metodo release non dealloca subito l’ oggetto. Il metodo release decrementa il retainCount.
Un oggetto puo` esser utilizzato da piu` metodi, da piu` processi (NSThread). Per capire cosa significa “oggetto utilizzato da piu` processi” proviamo a pensare ad un sacchetto che viene riempito e svuotato da piu` persone contemporaneamente. Ogni persona e` un processo che utilizza l’ oggetto.
Se inizialmente c’e` solo una persona che e` interessata all’ utilizzo dell’ oggetto il retainCount di questo oggetto e` a 1.
Se arriva un’altra persona e vuole anche lei utilizzare lo stesso sacchetto, questa aumenta il retainCount dell’ oggetto chiamando il metodo retain
[oggetto retain];
In questo modo il retainCount dell’ oggetto passa da 1 a 2, dicendo che ci sono due persone che stanno utilizzando l’ oggetto.
Arriva una terza persona e porta il retainCount da 2 a 3 tramite il comando [oggetto retain];
La prima persona non e` piu` interessata ad utilizzare il sacchetto e se ne va, prima di andarsene abbassa il retainCount tramite il comando
[oggetto release];
In questo modo il retainCount passa da 3 a 2.
Quando il retainCount divenza zero, viene automaticamente chiamato il metodo dealloc dell’oggetto, liberando subito la memoria.
Un esempio
Prendiamo il codice della lezione precedente: il nostro progetto Automobili.
Implementiamo sotto il nostro oggetto Car, il metodo dealloc. Questo metodo, come dicevo prima, viene chiamato quando il retainCount va a zero.
Quando verra` chiamato il metodo dealloc, comparira` una scritta “DEALLOCO macchina”
Come si nota dal log, quando il retainCount diventa zero (inutilizzo dell’ oggetto), questo viene deallocato.
Piccola bugia a fin di bene
Prima vi ho mostrato come fare il retain ed il release di stringhe. Ragionare con le stringhe e` , inizialmente, il modo migliore per prendere confidenza con i concetti di memoria. In realta` quando si fanno allocazioni di stringhe del tipo
NSString *pippo = @”Pippo”;
la stringa @”Pippo” viene messa nella memoria statica da parte del compilatore, cioe` non c’e` bisogno di deallocarla (o meglio.. non e` proprio possibile dellocarla).
Provate a vedere il retainCount della stringa pippo
Se proviamo a forzare il dealloc vediamo che riceviamo un errore
Questo perche` la stringa @”Pippo” e` dichiarata staticamente.
Un esempio invece nel quale viene utilizzata la memoria dinamica (e quindi e` necessario utilizzare retain/release) e` questo
Questi concetti e` normale non siano immediati. L’ esperienza vi permettera` di capire pian piano come viene gestita la memoria e soprattutto una approfondita lettura del pdf da me gia` da tempo consigliato.