In questo articolo analizzeremo un aspetto della programmazione per iphone che spesso è responsabile di lunghe ore di debug e di frustrazione. Stiamo parlando, ovviamente, della gestione della memoria.
È bene precisare che tutto quello che diremo qui si applica solo agli oggetti nativi dell’ Objective-C mentre per le variabili dichiarate con la sintassi C (int, char, float) restano valide le regole di questo linguaggio.
Una corretta e attenta gestione della memoria sui dispositivi mobili e, soprattutto, su ipod/iphone è di cruciale importanza, viste le ridotte capacità di elaborazione di questi device.
Proprio per le ridotte capacità di elaborazione è stato necessario privare i nostri iphone di una vera e propria Garbage Collection, utilizzando in sua vece un più complesso Autorelease Pool.
Ma quali sono le differenze? La GC monitora costantemente i nostri oggetti, i quali vengono eliminati solo su nostra richiesta o quando non esistono più riferimenti per accedere a quell’oggetto. Questo implica che se esiste un riferimento ad un oggetto, questo è sicuramente valido.
L’autorelease pool invece è l’esatto contrario, elimina tutti gli oggetti, tranne quelli per i quali abbiamo fatta esplicita richiesta che vengano mantenuti. Il motivo di tale approccio è facilmente intuibile, ed è da ricercare nel fatto che nei device attuali la memoria è un bene prezioso e ci non possiamo permettere che i nostri programmi la sprechino.
L’autorelase viene istanziato nel main delle nostre applicazioni, con questa istruzione:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
e lo possiamo considerare quindi alla stessa stregua del tristo mietitore pronto ad eliminare tutti gli oggetti ritenuti inutili, compito che svolge analizzando una particolare proprietà di ogni oggetto chiamata retainCount. In particolare vengono eliminati tutti quegli oggetti che hanno un valore di retainCount pari a zero.
Per far funzionare il nostro programma secondo le nostre aspettative dovremo quindi fare in modo che gli oggetti necessari abbiamo retainCount maggiore o uguale a 1.
Se per qualche ragione siamo interessati a sapere quando l’autorelease elimina i nostri oggetti, possiamo sfruttare il fatto che viene invocato in maniera trasparente il metodo dealloc sull’oggetto da eliminare, quindi eseguendo un opportuno override di questo metodo possiamo ottenere tutte le informazioni di cui abbiamo bisogno.
Quali sono quindi i momenti in cui retainCount varia?
- Quando una variabile viene inizializzata il suo retainCount viene incrementato di 1.
- I metodi alloc, copy, allocWithZone incrementano retainCount di 1.
- I metodi release, autorelease decrementano retainCount di 1. (autorelease lo fa in un momento non deterministico)
- Se non diversamente specificato il metodo autorelease viene invocato di default.
Vediamo qualche esempio:
TheClass *newObject = [[TheClass alloc] init];
In questo caso il retainCount di newObject sarà 1 per via del metodo alloc. (in realtà riceve anche un +1 per l’inizializzazione e un -1 per l’autorelease implicito)
L’area di memoria assegnata a questo oggetto quindi non potrà essere liberata, neanche quando usciremo dallo scope della variabile newObject. In questo caso avremo perso ogni riferimento a questo oggetto e avremo creato quello che viene chiamato leak.
NSString *myString = @"Hello World";
In questo caso il retainCount di myString sarà pari a zero, perchè +1 lo riceverà per via della inizializzazione e -1 per via dell’ autorelease implicito. Questo significa che la zona di memoria assegnata a myString verrà liberata dall’autoreleasePool nonappena questi lo riterrà opportuno, non prima del termine dell’esecuzione del metodo corrente però!
NSString *myString = [NSString stringWithFormat:@"HELLO WORLD"] autorelease];
Stesso caso del precedente ma con la chiamata esplicita di autorelease.
Facciamo un piccolo esempio per comprendere quando sia indispensabile la far propri questi meccanismi. Dichiariamo in una nostra classe una variabile (variabile di istanza)
NSString *myString
La inizializziamo in un metodo, e vi accediamo da un secondo.
- (void)viewDidLoad {
myString = [[NSString stringWithFormat:@"ciao"] autorelease];
[super viewDidLoad];
}
//..altro codice...
- (IBAction)otherMethod:(id)sender{
NSLog(myString);
}
Questo codice per quanto sembri corretto invece andrà in errore, perchè avendo chiamato il metodo autorelease sull’oggetto myString ne abbiamo portato il retainCount a zero, quindi viene eliminata dall’autorelease pool.
Un caso ancora più significativo avviene quando gli oggetti diventano parametri dei nostri metodi, vediamo questo codice:
//CLASSE 1
- (void)metodo1{
NSString *saluto = [NSString stringWithFormat:@"Ciao mondo"];
[classe2 metodo2:saluto]
}
//CLASSE2
NSString *newString;
- (void)metodo2:(NSString *)str{
newString = str;
}
- (void)metodo3{
NSLog(newString);
}
L’invocazione del metodo3 causerà un crash della nostra applicazione perchè l’oggetto stringa saluto definita nel metodo1, è di tipo autorelease e quindi verrà rimosso dall’autorelease pool. All’interno del metodo2 l’operazione newString=str non fa altro che assegnare al puntatore newString lo stesso indirizzo di str che era lo stesso di saluto (ricordiamoci che in fondo parliamo sempre di puntatori C ) quindi quando l’oggetto saluto verrà eliminato i riferimenti all’interno di newString non saranno più validi ed ogni tentativo di accesso produrrà il famoso errore EXC_BAD_ACCESS.
Alla prossima.













12 Responses to “L#008 – Objective-C (Parte III) Gestiamo la memoria”
3 Dicembre 2009
FabrizioCiao, sono un “più che roookie”, non capisco una cosa nell’ultimo esempio: perchè dici che “al puntatore newString lo stesso indirizzo di str che era lo stesso di saluto” ? Dov’è che *saluto si relaziona con str?
Perdona la domanda banale 🙂 e grazie.
3 Dicembre 2009
ignaziocGrazie per la segnalazione. Si tratta di un errore nel codice.
Il primo metodo chiama il secondo con questa riga:
[classe2 metodo2:saluto]
non con
[classe2 metodo2:myString]
come avevo indicato. (myString non è neanche definita!)
A questo punto risulta evidente che l’indirizzo di memoria è sempre lo stesso.
(che vuol dire “più che rookie”??)
3 Dicembre 2009
FabrizioIntendevo dire un “pivello” 🙂
Ok grazie, è chiarissimo ora. Ciao
3 Dicembre 2009
Clod75Ma siete grandiosi!!!! Questa lezione non solo è utilissima, ma è anche spiegata benissimo!!!! Complimenti!!!
11 Dicembre 2009
AntonioSalve,
ho iniziato da poco a programmare su iPhone e nei libri di testo che sto seguendo per una corretta gestione della memoria consigliano di porre a nil gli outlet nel viewDidUnload e rilasciare con release tutti gli attributi di classe in dealloc
Alcune volte ho visto che in dealloc invece di inviare il messaggio release all’oggetto lo si imposta a nil.
C’è qualche differenza tra impostare a nil un oggetto o inviare il messaggio di release?
inoltre in viewDidUnload solo gli outlet devo impostare a nil?
quando dichiaro nel file header una proprietà con property che differenza c’è tra retain e copy?
Grazie e complimenti per gli articoli, sono veramente chiari.
12 Dicembre 2009
Ignazioctutte domande molto interessanti,
“C’è qualche differenza tra impostare a nil un oggetto o inviare il messaggio di release?”
Porre un oggetto uguale a nil in pratica significa prendere un puntatore ad una struttura e farlo puntare a 0, questo significa che la memoria allocata per i membri della struttura non sarà più accessibile, neanche per effettuare il “free”, invece inviare un messaggio di release decrementa il retaincount, quando questi raggiunge il valore 0 viene chiamato (in un momento successivo) in modo automatico il metodo dealloc su questo oggetto dall’autorelease pool.
È bene specificare che nel caso di classi “personalizzate” è sempre necessario riscrivere il metodo dealloc, per far si che vengano tutte le risorse impegnate all’interno della classe.
“inoltre in viewDidUnload solo gli outlet devo impostare a nil?”
la guida dice “viewDidUnload: Called when the controller’s view is released from memory” quindi poi imposture a nil gli outlet come suggerito dalla stessa apple, ma per gli oggetti creati da te devi prevedere metodi specifici.
ti consiglio di leggere il paragrafo su viewDidUnload sulla UIviewControllerClassReference.
“quando dichiaro nel file header una proprietà con property che differenza c’è tra retain e copy?”
retain incrementa il retaincount, mentre copy crea una copia dell’oggetto e si diviene proprietari di questa copia.
12 Dicembre 2009
AntonioGrazie per la risposta,
Se ho ben capito la regola che ogni classe è proprietaria e responsabile degli oggetti che crea potrebbe essere descritta dalle seguenti situazioni:
Assumiamo che stia lavorando con classi e oggetti non personalizzati, ovvero quelli forniti nell’SDK di apple
impostare a nil quindi vuol dire liberare subito memoria, mentre portare il retainCount a 0 con il messaggio release è come se stessi marcando quell’oggetto come “da eliminare per liberare memoria” e quindi l’autorelease pool in un momento non deterministico richiama il dealloc per quell’oggetto e libera memoria.
Perciò in un UIViewController porre gli outlet a nil in viewDidUnload, e in dealloc mandare un messaggio di release a tutti gli attributi di classe della view, che questi siano outlet oppure no, è buona norma per una gestione corretta della memoria?
Se invece lavoro su classi personalizzate, che poi saranno utilizzate nelle view, devo assicurarmi di implementare il metodo dealloc che conterrà il codice per il release di tutti gli oggetti e attributi della classe personalizzata in modo tale da evitare i cosiddetti “memory leaks” che si verrebbero a creare qualora la view che utilizza l’oggetto personalizzato venga deallocata.
7 Febbraio 2010
Francesco_96Siete fantastici, non ero riuscito a capire bene questa parte dal libro che ho comprato ma con l’aiuto di questo articolo adesso è tutto chiarissimo! Complimenti!!!
10 Giugno 2010
Accedere a Flickr dalle nostre applicazioni iPhone (parte 4) | devAPP[…] leggere questo articolo a tal […]
14 Luglio 2010
Gestione della memoria in Objective-C per le nostre applicazioni iPhone, iPad (e Mac) | devAPP[…] parliamo di gestione della memoria nelle applicazioni iPhone (e iPad). Dopo un primo ottimo articolo sull’argomento, creato dal nostro Ignazio Calò, abbiamo pensato fosse meglio, viste le […]
23 Luglio 2010
Marioquande che si deve usare il retain invece del assign/copy?
18 Settembre 2010
PaoloCiao vorrei chiedervi un aiuto.
Dichiarando un nsarray nel file vievController.h (header).
Come faccio ad utilizzarlo correttamente senza errori all’interno del file viewcontroller.m?
Per esempio all’interno di un evento:
-(void) dimmi:(id)sender {
——–
}
Grazie