Obiettivo-C in breve gestione della memoria

La memoria deve essere allocata per ogni oggetto utilizzato dall'applicazione e deve essere deallocata al termine dell'applicazione per garantire che l'applicazione utilizzi la memoria nel modo più efficiente possibile. È importante comprendere l'ambiente di gestione della memoria di Objective-C per garantire che il programma non perda memoria o provi a fare riferimento a oggetti che non esistono più.

Conteggio dei riferimenti a un oggetto

A differenza di C #, l'Objective-C lo fa non usa la garbage collection. Invece, utilizza un ambiente di conteggio dei riferimenti che tiene traccia di quanti luoghi stanno usando un oggetto. Finché c'è almeno un riferimento all'oggetto, il runtime Objective-C si assicura che l'oggetto risieda in memoria. Tuttavia, se non ci sono più riferimenti all'oggetto, il runtime può rilasciare l'oggetto e utilizzare la memoria per qualcos'altro. Se si tenta di accedere a un oggetto dopo che è stato rilasciato, il programma si bloccherà molto probabilmente.

Esistono due modi che si escludono a vicenda per gestire i riferimenti agli oggetti in Objective-C:

  1. Invia manualmente metodi per incrementare / decrementare il numero di riferimenti a un oggetto.
  2. Lascia che il nuovo schema di conteggio dei riferimenti automatico (ARC) di Xcode 4.2 (e versioni successive) faccia al caso tuo.

ARC è il modo preferito per gestire la memoria in nuove applicazioni, ma è comunque importante capire cosa sta succedendo sotto il cofano. La prima parte di questo capitolo mostra come tracciare manualmente i riferimenti agli oggetti, quindi parleremo delle implicazioni pratiche di ARC.


Gestione manuale della memoria

Per sperimentare qualsiasi codice in questa sezione, devi disattivare il conteggio dei riferimenti automatico. Puoi farlo cliccando sul HelloObjectiveC progetto nel pannello di navigazione di Xcode:

Il progetto HelloObjectiveC nel pannello di navigazione

Si apre una finestra che consente di regolare le impostazioni di costruzione per il progetto. Discuteremo le impostazioni di costruzione nella seconda metà di questa serie. Per ora, tutto ciò che dobbiamo trovare è la bandiera ARC. Nel campo di ricerca nell'angolo in alto a destra, digita conteggio automatico dei riferimenti, e dovresti vedere le seguenti impostazioni:

Disabilitare il conteggio dei riferimenti automatici

Fai clic sulle frecce accanto a e cambiarlo in No disabilitare ARC per questo progetto. Questo ti permetterà di usare i metodi di gestione della memoria discussi nei paragrafi seguenti.

La gestione manuale della memoria (detta anche manuale retain-release o MMR) ruota intorno al concetto di proprietà "oggetto". Quando crei un oggetto, ti viene detto proprio l'oggetto: è tua responsabilità liberare l'oggetto quando hai finito. Questo ha senso, dal momento che non vorresti che qualche altro oggetto arrivasse e rilasciasse l'oggetto mentre lo stai usando.

La proprietà dell'oggetto è implementata tramite il conteggio dei riferimenti. Quando rivendichi la proprietà di un oggetto, aumenti il ​​suo conteggio di riferimento di uno, e quando abbandoni la proprietà, decrementi il ​​conteggio di riferimento di uno. In questo modo, è possibile garantire che un oggetto non venga mai liberato dalla memoria mentre un altro oggetto lo sta usando. NSObject e il protocollo NSObject definiscono i quattro metodi principali che supportano la proprietà dell'oggetto:

  • +(Id) alloc - Assegna memoria per una nuova istanza e rivendica la proprietà di tale istanza. Questo aumenta il numero di riferimenti dell'oggetto di uno. Restituisce un puntatore all'oggetto assegnato.
  • -(Id) trattenere - Rivendica la proprietà di un oggetto esistente. È possibile che un oggetto abbia più di un proprietario. Questo incrementa anche il conteggio dei riferimenti dell'oggetto. Restituisce un puntatore all'oggetto esistente.
  • -rilascio (void) - Abbandonare la proprietà di un oggetto. Questo decrementa il conteggio dei riferimenti dell'oggetto.
  • -(Id) autorelease - Abbandona la proprietà di un oggetto alla fine del blocco pool di autoritease corrente. Questo decrementa il conteggio dei riferimenti dell'oggetto, ma consente di continuare a utilizzare l'oggetto rimandando il rilascio effettivo fino a un momento successivo. Restituisce un puntatore all'oggetto esistente.

Per ogni alloc o conservare metodo che chiamate, è necessario chiamare pubblicazione o autorelease ad un certo punto lungo la linea. Il numero di volte in cui rivendichi un oggetto dovere uguale al numero di volte che lo rilasci. Chiamando un extra alloc/conservare risulterà in una perdita di memoria e chiamando un extra pubblicazione/autorelease proverà ad accedere ad un oggetto che non esiste, causando il crash del programma.

Tutte le interazioni tra gli oggetti, indipendentemente dal fatto che le stiate utilizzando in un metodo di istanza, getter / setter o una funzione autonoma, dovrebbero seguire il modello claim / use / free, come dimostrato nel seguente esempio:

Esempio di codice incluso: memoria manuale

int main (int argc, const char * argv []) // Rivendica l'oggetto. Person * frank = [[Person alloc] init]; // Usa l'oggetto. frank.name = @ "Frank"; NSLog (@ "% @", franco.name); // Libera l'oggetto. [versione franca]; ritorno 0; 

Il [Assegnazione persona] call set Francoil conteggio di riferimento a uno, e [versione franca] decrementa a zero, permettendo al runtime di eliminarlo. Si noti che provando a chiamare un altro [versione franca] risulterebbe in un incidente, dal momento che Franco la variabile non esiste più nella memoria.

Quando si utilizzano oggetti come variabile locale in una funzione (ad esempio, l'esempio precedente), la gestione della memoria è piuttosto semplice: basta chiamare pubblicazione alla fine della funzione. Tuttavia, le cose possono diventare più difficili quando si assegnano le proprietà all'interno dei metodi setter. Ad esempio, considera la seguente interfaccia per una nuova classe chiamata Nave:

Esempio di codice incluso: memoria manuale - riferimento debole

// Ship.h #import "Person.h" @interface Ship: NSObject - (Person *) capitano; - (void) setCaptain: (Person *) theCaptain; @fine

Questa è una classe molto semplice con metodi di accesso definiti manualmente per a Capitano proprietà. Dal punto di vista della gestione della memoria, ci sono diversi modi in cui il setter può essere implementato. Innanzitutto, prendi il caso più semplice in cui il nuovo valore viene semplicemente assegnato a una variabile di istanza:

// Ship.m #import "Ship.h" @implementation Ship Person * _captain;  - (Persona *) capitano return _captain;  - (void) setCaptain: (Person *) theCaptain _captain = theCaptain;  @fine

Questo crea a riferimento debole perché il Nave l'istanza non assume la proprietà di il capitano oggetto quando viene assegnato. Anche se non c'è nulla di sbagliato in questo, e il tuo codice funzionerà ancora, è importante capire le implicazioni dei riferimenti deboli. Considera il seguente frammento:

#importare  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * frank = [[Person alloc] init]; Ship * discoveryOne = [[Ship alloc] init]; frank.name = @ "Frank"; [discoveryOne setCaptain: franco]; NSLog (@ "% @", [discoveryOne captain] .name); [versione franca]; // [discoveryOne captain] non è più valido. NSLog (@ "% @", [discoveryOne captain]. Name); [discoveryOne release];  restituisce 0; 

chiamata [versione franca] decrementi FrancoIl conteggio di riferimento è zero, il che significa che al runtime è consentito deallocarlo. Ciò significa che [discoveryOne captain] ora punta a un indirizzo di memoria non valido, anche se discoveryOne mai rilasciato.

Nel codice di esempio fornito, osserverai che abbiamo aggiunto un dealloc sovrascrittura del metodo nella classe Person. dealloc viene chiamato quando la memoria sta per essere rilasciata. In genere dovremmo gestire dealloc e rilasciare tutti i riferimenti di oggetti nidificati che teniamo. In questa istanza rilasceremo la proprietà del nome annidata che conserviamo. Avremo più cose da dire dealloc nel prossimo capitolo.

Se dovessi provare ad accedere alla proprietà, il tuo programma molto probabilmente si arresterebbe. Come puoi vedere, devi essere molto attento nel rintracciare i riferimenti agli oggetti quando usi proprietà debolmente referenziate.

Riferimento debole al valore del capitano

Per relazioni di oggetti più solide, puoi usare riferimenti forti. Questi vengono creati rivendicando l'oggetto con a conservare chiama quando è assegnato:

Esempio di codice incluso: memoria manuale - riferimento forte

- (void) setCaptain: (Person *) theCaptain [_captain autorelease]; _captain = [theCaptain retain]; 

Con un riferimento forte, non importa ciò che gli altri oggetti stanno facendo con il il capitano oggetto, dal conservare si assicura che rimarrà intorno finchè il Nave l'istanza ne ha bisogno. Certo, devi bilanciare il conservare chiama rilasciando il vecchio valore - se non lo facessi, il tuo programma perderebbe memoria ogni volta che qualcuno assegnasse un nuovo valore al Capitano proprietà.

Forte riferimento al valore del capitano

Oggetti con rilascio automatico

Il autorelease metodo funziona molto simile pubblicazione, tranne che il conteggio dei riferimenti dell'oggetto non viene decrementato immediatamente. Invece, il runtime attende fino alla fine della corrente @autoreleasepool blocco per chiamare un normale pubblicazione sull'oggetto. Questo è il motivo per cui il main.m il modello è sempre avvolto in un @autoreleasepool-si assicura che tutti gli oggetti siano accodati autorelease le chiamate sono in realtà rilasciato alla fine del programma:

int main (int argc, const char * argv []) @autoreleasepool // Inserisci il codice per creare e autorizzare gli oggetti qui. NSLog (@ "Hello, World!"); // Tutti gli oggetti autoreleased sono * effettivamente * rilasciati qui.  restituisce 0; 

L'idea alla base dell'auto-rilascio è di dare al proprietario di un oggetto la possibilità di abbandonare la proprietà senza distruggere effettivamente l'oggetto. Questo è uno strumento necessario in situazioni in cui è necessario restituire un nuovo oggetto da un metodo factory. Ad esempio, si consideri il seguente metodo di classe definito in Ship.m:

+ (Nave *) shipWithCaptain: (Person *) theCaptian Ship * theShip = [[Ship alloc] init]; [theShip setCaptain: theCaptian]; restituire theShip; 

Questo metodo crea, configura e restituisce un nuovo Nave esempio. Ma c'è un problema serio con questa implementazione: si traduce in una perdita di memoria. Il metodo non rinuncia mai alla proprietà dell'oggetto e ai chiamanti di shipWithCaptain non sanno che hanno bisogno di liberare l'oggetto restituito (e non dovrebbero farlo). Di conseguenza, il la nave l'oggetto non verrà mai rilasciato dalla memoria. Questa è esattamente la situazione autorelease è stato progettato per. La corretta implementazione è mostrata qui:

+ (Nave *) shipWithCaptain: (Person *) theCaptian Ship * theShip = [[Ship alloc] init]; [theShip setCaptain: theCaptian]; return [theShip autorelease]; // deve rinunciare alla proprietà! 

utilizzando autorelease invece di un immediato pubblicazione lascia che il chiamante usi l'oggetto restituito mentre ancora rinunci alla sua proprietà nella posizione corretta. Se ti ricordi dal capitolo Tipi di dati, abbiamo creato tutte le nostre strutture di dati della Fondazione utilizzando metodi di fabbrica di livello base. Per esempio:

NSSet * crew = [NSSet setWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", nil];

Il setWithObjects metodo funziona esattamente come il shipWithCaptain metodo descritto nell'esempio precedente. Restituisce un oggetto autorelued in modo che il chiamante possa utilizzare l'oggetto senza preoccuparsi della gestione della memoria. Si noti che esistono metodi di istanza equivalenti per inizializzare gli oggetti di Foundation. Ad esempio, il equipaggio l'oggetto nell'ultimo campione può essere creato manualmente come segue:

// Crea e rivendica il set. NSSet * crew = [[NSSet alloc] initWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", nil]; // Usa il set ... // Rilascia il set. [versione dell'equipaggio];

Tuttavia, utilizzando metodi di classe come setWithObjects, arrayWithCapacity, ecc., è generalmente preferito rispetto al alloc/dentro.

Attributi di mantenimento a rilascio manuale

Affrontare la memoria dietro le proprietà di un oggetto può essere un compito noioso e ripetitivo. Per semplificare il processo, Objective-C include diversi attributi di proprietà per automatizzare le chiamate di gestione della memoria nelle funzioni accessorie. Gli attributi descritti nel seguente elenco definiscono il comportamento del setter in Manuale ambienti di conteggio di riferimento. Fare non prova ad usare assegnare e conservare in un ambiente di conteggio dei riferimenti automatico.

  • assegnare - Memorizza un puntatore diretto al nuovo valore senza alcuno conservare / pubblicazione chiamate. Questo è l'equivalente automatico di un riferimento debole.
  • conservare - Memorizza un puntatore diretto sul nuovo valore, ma chiama pubblicazione sul vecchio valore e conservare su quello nuovo. Questo è l'equivalente automatico di un riferimento forte.
  • copia - Crea una copia del nuovo valore. Copiando la proprietà delle attestazioni della nuova istanza, quindi il valore precedente viene inviato a pubblicazione Messaggio. Questo è come un forte riferimento a una nuova istanza dell'oggetto. Generalmente, la copia viene utilizzata solo per tipi immutabili come NSString.

Come un semplice esempio, esaminare la seguente dichiarazione di proprietà:

@property (retain) Person * capitan;

Il conservare attributo dice l'associato @sintetizzare dichiarazione per creare un setter che assomigli a qualcosa:

- (void) setCaptain: (Person *) theCaptain [_captain release]; _captain = [theCaptain retain]; 

Come puoi immaginare, usa gli attributi di gestione della memoria con @proprietà è molto più semplice della definizione manuale di getter e setter per ogni proprietà di ogni classe personalizzata definita dall'utente.


Conteggio di riferimento automatico

Ora che hai una gestione del conteggio dei riferimenti, della proprietà degli oggetti e dei blocchi autorelease, puoi completamente dimenticarti di tutto. A partire da Xcode 4.2 e iOS 4, Objective-C supporta il conteggio automatico dei riferimenti (ARC), che è un passo di pre-compilazione che aggiunge le necessarie chiamate di gestione della memoria per te.

Se ti è capitato di aver disattivato ARC nella sezione precedente, dovresti riaccenderlo. Ricorda che puoi farlo cliccando sul HelloObjectiveC progetto nel pannello di navigazione, selezionando il Costruisci le impostazioni scheda e ricerca conteggio automatico dei riferimenti.

Abilitazione del conteggio di riferimento automatico nelle impostazioni di costruzione del progetto

Il conteggio automatico dei riferimenti funziona esaminando il codice per capire per quanto tempo un oggetto deve restare e inserire conservare, pubblicazione, e autorelease metodi per assicurarti che sia deallocato quando non è più necessario, ma non mentre lo stai usando. Per non confondere l'algoritmo ARC, tu non devi fare qualsiasi conservare, pubblicazione, o autorelease ti chiama. Ad esempio, con ARC, è possibile scrivere il seguente metodo e nessuno dei due la naveil capitano sarà trapelato, anche se non abbiamo rinunciato esplicitamente alla proprietà di loro:

Esempio di codice incluso: ARC

+ (Nave *) nave Nave * theShip = [[Allocazione nave] init]; Person * theCaptain = [[Person alloc] init]; [theShip setCaptain: theCaptain]; restituire theShip; 

Attributi ARC

In un ambiente ARC, non si dovrebbe più usare il assegnare e conservare attributi di proprietà. Invece, dovresti usare il debole e forte attributi:

  • debole - Specificare una relazione non proprietaria dell'oggetto di destinazione. Questo è molto simile assegnare; tuttavia, ha la comoda funzionalità di impostare la proprietà su zero se il valore è deallocato. In questo modo, il tuo programma non si arresterà in modo anomalo quando tenta di accedere a un indirizzo di memoria non valido.
  • forte - Specificare una relazione proprietaria con l'oggetto di destinazione. Questo è l'equivalente ARC di conservare. Assicura che un oggetto non venga rilasciato finché è assegnato alla proprietà.

Puoi vedere la differenza tra debole e forte usando l'implementazione del nave metodo di classe dalla sezione precedente. Per creare un riferimento forte al capitano della nave, l'interfaccia per Nave dovrebbe apparire come il seguente:

// Ship.h #import "Person.h" @interface Nave: NSObject @property (strong) Person * captain; + (Nave *) nave; @fine

E l'implementazione Nave dovrebbe assomigliare a:

// Ship.m #import "Ship.h" @implementation Ship @synthesize captain = _captain; + (Nave *) nave Nave * theShip = [[Allocazione nave] init]; Person * theCaptain = [[Person alloc] init]; [theShip setCaptain: theCaptain]; restituire theShip;  @fine

Quindi, puoi cambiare main.m per mostrare il capitano della nave:

int main (int argc, const char * argv []) @autoreleasepool Ship * ship = [Ship ship]; NSLog (@ "% @", [capitano della nave]);  restituisce 0; 

Questo produrrà qualcosa di simile nella console, che ci dice che il il capitano oggetto creato nel nave il metodo di classe esiste ancora.

Ma prova a cambiare il (forte) attributo di proprietà a (debole) e ricompilare il programma. Ora dovresti vedere (nullo) nel pannello di output. Il riferimento debole non garantisce che il il capitano variabile si attacca intorno, quindi una volta arrivato alla fine del nave metodo di classe, l'algoritmo ARC pensa che possa disporre di il capitano. Di conseguenza, il Capitano la proprietà è impostata su zero.


Sommario

La gestione della memoria può essere un problema, ma è una parte essenziale della creazione di un'applicazione. Per le applicazioni iOS, l'allocazione / eliminazione degli oggetti corretta è particolarmente importante a causa delle limitate risorse di memoria dei dispositivi mobili. Ne parleremo di più nella seconda parte di questa serie, iOS in modo succinto.

Fortunatamente, il nuovo schema ARC rende la gestione della memoria molto più semplice per lo sviluppatore medio. Nella maggior parte dei casi, è possibile trattare un progetto ARC proprio come la garbage collection in un programma C #, basta creare i propri oggetti e lasciare che ARC ne disponga a sua discrezione. Si noti, tuttavia, che questa è solo una somiglianza pratica: l'implementazione ARC è molto più efficiente della garbage collection.

Questa lezione rappresenta un capitolo di Objective-C, un eBook gratuito del team di Syncfusion.