Programmazione iOS e Mac – Gestione della memoria

Un oggetto è caratterizzato da un suo ciclo di vita

1)Allocazione ——————————————-> l’oggetto viene creato

2)Riceve messaggi e compie azioni——————> L’oggetto “vive”

3)deallocazione——————————————>L’oggetto “muore”

Quando un oggetto viene deallocato la sua memoria viene resa disponibile per un altro oggetto che dovrà nascere

In Objective-C ( e in tutti i linguaggi simili al C ) l’aspetto inerente la gestione della memoria occupa un posto rilevante nella progettazione di un programma.

La memoria del’essere allocata e poi liberata quando non più utilizzata

Se il programma continua ad allocare memoria senza poi liberarla finirà con consumare tutta la risorsa hardware fino al punto di crashare.
Occorre fare altrettanto attenzione a non usare la memoria appena liberata in quanto i dati in essa contenuti potrebbero non essere più significativi o ancora peggio taluna memoria potrebbe essere stata allocata da un altro processo, il che porterebbe alla corruzione dei nuovi dati.

Reference Counting

E’ la tecnica utilizzata da Cocoa per la gestione della memoria

Tale tecnica permette di tenere traccia dell’uso degli oggetti.Ogni oggetto ha un proprio contatore (reference count) che ne indica l’utilizzo . Questo contatore viene impostato ad uno una volta che l’oggetto viene creato e viene decrementato di uno quando non serve più

Reference Counting è definito in NSObject e fin tanto che il suo valore è > 0, l’oggetto è vivo e valido

I metodi alloc e copy creano oggetti con retain count ==1

retain incrementa retain
release decrementa retain
Solo quando retain count è 0 l’oggetto è distrutto
dealloc provoca la distruzione dell’oggetto direttamente anche se il suo retaincount ha valore >0

In realtà mentre il retainCount di tutti gli oggetti mutabili viene impostato inizialmente ad uno,per gli oggetti immutabili, come NSString,NSArray e NSDictionary, il retainCount viene inizializzato con il valore più alto consentito per un numero intero positivo. Questo perchè non è consentito effettuare l’operazione di retain sugli oggetti immutabili.
Per gli oggetti immutabili dunque non è neanche necessario utilizzare il metodo release .

Per incrementare il reference count occorre inviare all’oggetto il messaggio retain, mentre per decrementarlo occorre inviargli il messaggio release.

Quando il contatore raggiunge il valore 0, Objective-C invia automaticamente il messaggio dealloc all’oggetto in questione.
Il programmatore può ridefinire la dealloc se ritiene che l’oggetto prima di essere eliminato debba rilasciare a sua volta delle risorse.

Se si vuole conoscere ad un certo momento il valore del counter si invia all’oggetto il messaggio retainCount.

Le difficoltà nella gestione della memoria subentrano quando un oggetto è referenziato (o posseduto) da altre entità (compreso anche main() ).
Consideriamo l’esempio seguente:

gli oggetti A e B vengono creati nel main() e quindi i rispettivi retain count valgono 1.

Ad un certo punto l’oggetto A prende possesso dell’oggetto B

Senza alcuna precauzione, un tentativo da parte di ObjA di eliminare ObjB porterebbe a conseguenze disastrose in quanto posseduto dal main() e viceversa. Le opportune precauzioni sono dunque da prendersi all’interno dei metodi setter attraverso l’uso corretto delle retain e release.

-- interfaccia A -----
@interface ObjA: NSObject
{

ObjB *refB;

}

-(void)setObj:(ObjB *)newObjB;
@end

 implementazione A
@implementation ObjA
-(void)setObj:(ObjB*)newObjB

{
   refB = newObjB;

}

@end

Una release da parte di main() comporta che refB punti ad una zona non più valida con conseguenze potenzialmente disastrose

Una soluzione immediata consiste nell’innalzare di 1 il retain count. In questo modo chi dei due (main() o objA) cerchi di liberarsene non danneggerà l’altro.
Riscriviamo quindi l’implementazione del metoso setObj:

– implementazione A —

@implementation ObjA

-(void)setObj:(ObjB *)newObjB

{
   refB = [newObjB retain];

}

@end

Il metodo retain restituisce lo stesso oggetto a cui è applicato incrementandone il contatore

Se a questo punto ad esempio il main invoca una release, l’oggetto ObjB non verrà distrutto perchè il suo contatore non sarà 1 ma essendo pari a 2 verrà decrementato di 1 restando vivo e posseduto tranquillamente da ObjA.

Peccato che questa soluzione, apparentemente corretta, nasconda un effetto collaterale poco piacevole. Immaginiamo la seguente situazione:

il main() dopo aver rilasciato ObjB, crea un nuovo oggetto sempre di tipo ObjB

A questo punto si assegna a ObjA il nuovo ObjB con il conseguente risultato

il vecchio ObjB non viene deallocato poichè il suo contatore vale 1 e quindi una porzione di memoria rimarrà sempre impegnata inutilmente dal vecchio ObjB.

Cerchiamo quindi una soluzione alla seconda anomalia. Possiamo riscrivere il metodo così

— implementazione A —

@implementation ObjA

-(void)setObj:(ObjB *)newObjB {

   [refB release];

refB = [newObjB retain];

}

@end

Rilasciamo poi objB da main() mediante il messaggio

[obj release];

Il contatore di ObjB da 2 scende a 1. Assegnamo per la seconda volta a refB l’oggetto ObjB secondo il messaggio seguente

[objA setObj:[objA refB]];

La soluzione finale che garantisce l’assegnamento protetto è la seguente

— implementazione A —-

@implementation ObjA

-(void)setObj:(ObjB *)newObjB {

   [newObjB retain];
   [refB release];
   refB = newObjB;

}

@end

La chiamata di un metodo che include alloc,copy o new ritorna un oggetto che è conservato ,ciò vuol dire che bisogna rilasciare esplicitamente l’oggetto alla fine del suo utilizzo richiamando una release.

Tutti gli altri metodi per la creazione di un oggetto ritornano un oggetto autorelease

PoolAutorelease 

Contiene tutti gli oggetti che ricevono il messaggio autorelease

NSAutorelease

Oggetto definito nel framework Cocoa che serve per la gestione del pool di autorelease

Il pool di autorelease contiene tutti gli oggetti che hanno ricevuto il messaggio di autorelease e quando viene deallocato invia un messaggio di release a tutti gli oggetti in esso contenuto

Un oggetto puo’ essere inserito in un pool più volte,in tal caso riceverà tanti messaggi di autorelease quante sono le volte che è stato inserito nel pool

E’ possibile all’interno di una stessa applicazione definire più pool

Quando si invia un messaggio di autorelease ad un oggetto si associa la durata dell’oggetto alla durata del pool. L’oggetto,se non già rilasciato in precedenza, verrà rilasciato quando il messaggio di realease viene inviato al pool.

E’ possibile creare un pool in qualunque momento all’interno di un programma :

NSAutoreleasePool  *pool=[[NSAutoreleasePool alloc] init]

ES

#import<C0c0a/Cocoa.h>

#import “ClasseX.h”

int main (int argc,char * argv[]) {

      NSAutoreleasePool  *pool =[[NSAutoreleasePool alloc] init];

      ClasseX *classeX = [[ClasseX alloc]init];

      [classeX autorelease];

      [classeX notifica:@”La gestione dellamemoria è importante!”];

      [pool release];

return 0;

}

Esempio: utilizzo di più pool (non innestati) all’interno di un programma

#import <Cocoa/Cocoa.h>

#import “ClasseX.h”

int main (int argc,char * argv[])
{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

ClasseX *classeA = [[ClasseX alloc] init]; [classeA autorelease];
[classeA notifica:@”La gestione della memoriaè importante!”];

      [pool release];

E’ possibile utilizzare l’autorelease anche nei metodi

#import “ClasseX.h”
@implementation CalsseX
-(void) notifica : (NSString*) testo {

testo2 = [[NSString alloc] initWithFormat:@”Il testo è : %@”,testo];

[testo2 autorelease];

      NSLog(testo2);
@end
Dalla prossima lezione inizieremo a interagire con la sintassi dell' objective-c