L#008 – Objective-C (Parte III) Gestiamo la memoria
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.


















Ciao, 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?
e grazie.
Perdona la domanda banale