Gestione della memoria Obj-C

La gestione della memoria è uno dei processi più importanti in qualsiasi linguaggio di programmazione. È il processo mediante il quale la memoria degli oggetti viene allocata quando sono richiesti e deallocati quando non sono più necessari.

La gestione della memoria degli oggetti è una questione di prestazioni; se un'applicazione non libera oggetti non necessari, il suo footprint di memoria aumenta e le prestazioni ne risentono.

Le tecniche di gestione della memoria Objective-C possono essere ampiamente classificate in due tipi.

  • "Rilascio a ritenzione manuale" o MRR
  • "Conteggio automatico dei riferimenti" o ARC

"Rilascio a ritenzione manuale" o MRR

In MRR, gestiamo esplicitamente la memoria tenendo traccia degli oggetti da soli. Ciò viene implementato utilizzando un modello, noto come conteggio dei riferimenti, fornito dalla classe Foundation NSObject insieme all'ambiente di runtime.

L'unica differenza tra MRR e ARC è che il mantenimento e il rilascio vengono gestiti da noi manualmente nel primo mentre vengono gestiti automaticamente nel secondo.

La figura seguente rappresenta un esempio di come funziona la gestione della memoria in Objective-C.

Il ciclo di vita della memoria dell'oggetto di Classe A è mostrato nella figura sopra. Come puoi vedere, il conteggio di conservazione è mostrato sotto l'oggetto, quando il conteggio di conservazione di un oggetto diventa 0, l'oggetto viene liberato completamente e la sua memoria viene deallocata per altri oggetti da utilizzare.

L'oggetto di classe A viene prima creato utilizzando il metodo alloc / init disponibile in NSObject. Ora, il conteggio di conservazione diventa 1.

Ora, la classe B conserva l'oggetto della classe A e il conteggio di conservazione dell'oggetto della classe A diventa 2.

Quindi, la classe C crea una copia dell'oggetto. Ora, viene creato come un'altra istanza di Classe A con gli stessi valori per le variabili di istanza. Qui, il conteggio di conservazione è 1 e non il conteggio di conservazione dell'oggetto originale. Questo è rappresentato dalla linea tratteggiata nella figura.

L'oggetto copiato viene rilasciato dalla Classe C utilizzando il metodo di rilascio e il conteggio di conservazione diventa 0 e quindi l'oggetto viene distrutto.

Nel caso dell'Oggetto di Classe A iniziale, il conteggio dei trattenuti è 2 e deve essere rilasciato due volte affinché possa essere distrutto. Questo viene fatto dalle dichiarazioni di rilascio della Classe A e della Classe B che decrementa il conteggio di conservazione a 1 e 0, rispettivamente. Infine, l'oggetto viene distrutto.

Regole di base dell'MRR

  • Possediamo qualsiasi oggetto che creiamo: creiamo un oggetto utilizzando un metodo il cui nome inizia con "alloc", "new", "copy" o "mutableCopy"

  • Possiamo assumere la proprietà di un oggetto utilizzando ritenzione: normalmente è garantito che un oggetto ricevuto rimanga valido all'interno del metodo in cui è stato ricevuto, e quel metodo può anche restituire in sicurezza l'oggetto al suo invocatore. Usiamo Retain in due situazioni:

    • Nell'implementazione di un metodo di accesso o di un metodo init, per assumere la proprietà di un oggetto che vogliamo memorizzare come valore di una proprietà.

    • Per evitare che un oggetto venga invalidato come effetto collaterale di qualche altra operazione.

  • Quando non ne abbiamo più bisogno, dobbiamo rinunciare alla proprietà di un oggetto che possediamo: rinunciamo alla proprietà di un oggetto inviandogli un messaggio di rilascio o un messaggio di rilascio automatico. Nella terminologia Cocoa, la rinuncia alla proprietà di un oggetto viene quindi generalmente definita "rilascio" di un oggetto.

  • Non devi rinunciare alla proprietà di un oggetto che non possiedi: questo è solo un corollario delle regole della politica precedente dichiarate esplicitamente.

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
  [super dealloc];
}

@end

int main() {
   
   /* my first program in Objective-C */
   SampleClass *sampleClass = [[SampleClass alloc]init];
   [sampleClass sampleMethod];
   
   NSLog(@"Retain Count after initial allocation: %d", 
   [sampleClass retainCount]);
   [sampleClass retain];
   
   NSLog(@"Retain Count after retain: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"Retain Count after release: %d", [sampleClass retainCount]);
   [sampleClass release];
   NSLog(@"SampleClass dealloc will be called before this");
   
   // Should set the object to nil
   sampleClass = nil;
   return 0;
}

Quando compiliamo il programma sopra, otterremo il seguente output.

2013-09-28 04:39:52.310 demo[8385] Hello, World!
2013-09-28 04:39:52.311 demo[8385] Retain Count after initial allocation: 1
2013-09-28 04:39:52.311 demo[8385] Retain Count after retain: 2
2013-09-28 04:39:52.311 demo[8385] Retain Count after release: 1
2013-09-28 04:39:52.311 demo[8385] Object deallocated
2013-09-28 04:39:52.311 demo[8385] SampleClass dealloc will be called before this

"Conteggio automatico dei riferimenti" o ARC

In Automatic Reference Counting o ARC, il sistema utilizza lo stesso sistema di conteggio dei riferimenti di MRR, ma inserisce le richieste del metodo di gestione della memoria appropriato in fase di compilazione. Siamo fortemente incoraggiati a utilizzare ARC per nuovi progetti. Se usiamo ARC, in genere non è necessario comprendere l'implementazione sottostante descritta in questo documento, sebbene in alcune situazioni possa essere utile. Per ulteriori informazioni su ARC, vedere Transitioning to ARC Release Notes.

Come accennato in precedenza, in ARC, non è necessario aggiungere metodi di rilascio e mantenimento poiché ciò sarà curato dal compilatore. In realtà, il processo alla base di Objective-C è sempre lo stesso. Utilizza internamente le operazioni di conservazione e rilascio rendendo più facile per lo sviluppatore scrivere codice senza preoccuparsi di queste operazioni, il che ridurrà sia la quantità di codice scritto che la possibilità di perdite di memoria.

C'era un altro principio chiamato garbage collection, che viene utilizzato in Mac OS-X insieme a MRR, ma dalla sua deprecazione in OS-X Mountain Lion, non è stato discusso insieme a MRR. Inoltre, gli oggetti iOS non hanno mai avuto la funzione di raccolta dei rifiuti. E con ARC, non è possibile utilizzare la garbage collection anche in OS-X.

Ecco un semplice esempio di ARC. Nota che questo non funzionerà con il compilatore online poiché non supporta ARC.

#import <Foundation/Foundation.h>

@interface SampleClass:NSObject
- (void)sampleMethod;
@end

@implementation SampleClass
- (void)sampleMethod {
   NSLog(@"Hello, World! \n");
}

- (void)dealloc  {
  NSLog(@"Object deallocated");
}

@end

int main() {
   /* my first program in Objective-C */
   @autoreleasepool {
      SampleClass *sampleClass = [[SampleClass alloc]init];
      [sampleClass sampleMethod];
      sampleClass = nil;
   }
   return 0;
}

Quando compiliamo il programma sopra, otterremo il seguente output.

2013-09-28 04:45:47.310 demo[8385] Hello, World!
2013-09-28 04:45:47.311 demo[8385] Object deallocated