Il nuovo compilatore Apple LLVM 3.0, introdotto con Xcode 4.2, introduce una funzionalità chiamata Automatic Reference Counting (ARC) estremamente interessante per noi sviluppatori. Essa ci aiuta a gestire in modo corretto la memoria di un’applicazione, permettendoci di creare applicazioni migliori e più robuste.
Prima di ARC, dovevamo chiamare manualmente e nei posti giusti i metodi retain/release/autorelease per assicurarci che gli oggetti rimanessero “in vita” esattamente quanto ci serviva. Purtroppo, dimenticare o inserire nel posto errato anche solo una chiamata a questi metodi dava origine a sprechi di memoria (che tipicamente crescono man mano che si usa l’applicazione) o addirittura a crash, cosa assolutamente intollerabile per un’applicazione professionale.
Grazie ad ARC, gran parte di questo lavoro viene ora fatto in automatico e in modo migliore di quanto potremmo fare noi stessi. Vediamo come e perché.
Introduzione
Innanzitutto occorre fare una precisazione: ARC non è un garbage collector. Il garbage collector funziona a run-time, cioè durante l’esecuzione del programma. ARC, al contrario, lavora a compile-time, cioè durante la compilazione. La differenza è fondamentale: vediamo perché.
Il garbage collector è a tutti gli effetti un “processo” che analizza continuamente la memoria alla ricerca di zone che non vengono più utilizzate e che dunque possono essere deallocate. Questo ha le seguenti implicazioni:
- Il garbage collector consuma cicli di CPU.
- L’istante in cui un oggetto verrà rilasciato non è predicibile.
ARC, invece, lavora a compile-time. In pratica, va lui stesso ad aggiungere al posto nostro le istruzioni necessarie per la gestione della memoria. D’altronde: chi meglio del compilatore sa dove devono essere inserite?
ARC, dunque, per certi versi è meglio di un garbage collector, anche se vedremo che ha alcuni limiti (per i più impazienti: non gestisce i retain cycle).
ARC è disponile solo a partire da Xcode 4.2 e solo quando si usa il compilatore Apple LLVM 3.0. Inoltre, il deployment target deve essere impostato almeno a iOS 4 o a OS X 10.6, sebbene alcune funzionalità (le weak reference) siano supportate solo a partire da iOS 5 e OS X 10.7. ARC, inoltre, può gestire solo oggetti e blocchi Objective-C, dunque se per qualche motivo ci trovassimo ad utilizzare le vecchie malloc dovremo continuare a liberarne la memoria manualmente.
In dettaglio
Il compilatore, quando ARC è abilitato, sintetizza in automatico le chiamate ai metodi retain/release/autorelease. Ad esempio questo codice:
Foo *foo = [[Foo alloc] init];
// do something with foo
return;
viene automaticamente trasformato in:
Foo *foo = [[Foo alloc] init];
// do something with foo
objc_release(foo); // funzionalmente equivalente alla vecchia [foo release];
return;
In pratica non dobbiamo più ricordarci di mettere la release, perché ci pensa il compilatore. Non solo: abbiamo la garanzia che lui la metta nel punto giusto, ovvero quando la variabile non viene più utilizzata, non un attimo prima e non un attimo dopo.
Un bel vantaggio: ARC riduce la possibilità che si verifichino quei nostri errori che si manifestavano in memory leak (zone di memoria occupate e mai più rilasciate), dangling pointer (puntatori che si riferiscono ad aree di memoria non più valide), double free (zone di memoria rilasciate più volte) e addirittura crash dell’applicazione.
Una curiosità. Perché il compilatore ha inserito una chiamata a funzione, objc_release(), al posto di una chiamata al metodo release? Semplicemente perché, pur essendo funzionalmente equivalente, una chiamata a funzione viene eseguita più rapidamente rispetto all’invio di un messaggio. Non solo: il compilatore la può individuare con maggiore semplicità e fare delle ulteriori ottimizzazioni. In questo articolo non ci interessano i dettagli, però è interessante osservare che il codice scritto con ARC tipicamente è addirittura più efficiente di quello che potremmo scrivere a mano.
Ma torniamo a noi. ARC permette di rimuovere molto codice che prima eravamo obbligati a scrivere. Ad esempio, nella maggior parte dei casi potremo evitare di scrivere i vecchi metodi dealloc, perché il compilatore è in grado di sintetizzarli per noi. Addio dunque a queste vecchie istruzioni:
- (void)dealloc {
[foo release];
[bar release];
[baz release];
[qux release];
[super dealloc];
}
Non si tratta di un vantaggio da poco. Quante volte, ad esempio, ci è capitato di dimenticare di rilasciare una variabile di istanza o di chiamare il metodo dealloc su super?
In sintesi, usando ARC abbiamo questi vantaggi:
- Codice più compatto e meno istruzioni da scrivere.
- Codice più veloce.
- Codice con meno errori e dunque meno memory leak.
Prima di concludere il paragrafo, vogliamo evidenziare che con ARC non solo non serve più chiamare i vecchi metodi retain, release, autorelease e retainCount, ma addirittura diventa vietato. Infatti, se provassimo ad inserirli ci compariranno degli errori di compilazione piuttosto espliciti, come ad esempio “ARC forbids explicit message send of ‘retain'”.
Introduzione ai nuovi tipi di ownership
Prima di procedere dobbiamo fare una parentesi più tecnica, fondamentale per capire il seguito dell’articolo e per padroneggiare davvero ARC.
ARC introduce questi nuovi “lifetime qualifier” per gli oggetti:
- strong
- weak
- unsafe unretained
- autoreleasing
Essi si usano nelle dichiarazioni in questo modo:
Foo __strong *obj1 = ...;
Foo __weak *obj2 = ...;
Foo __autoreleasing *obj3 = ...;
Foo __unsafe_unretained *obj4 = ...;
In estrema sintesi:
- strong è il qualificatore di default.
- weak specifica una weak reference che viene automaticamente posta a nil quando l’oggetto a cui punta è stato deallocato.
- unsafe unretained è simile a weak, ma non viene automaticamente posto a nil.
- autoreleasing denota gli argomenti che sono passati per riferimento e che sono rilasciati automaticamente dopo il return.
Spiegazione un po’ fumosa? Facciamo un esempio pratico. Consideriamo questo codice:
Foo *obj1 = [[Foo alloc] init];
Foo *obj2 = obj1;
Foo __unsafe_unretained *obj3 = obj1;
// do something with obj1, obj2 and obj3
return;
Come viene “tradotto” da ARC? Indicativamente così:
Foo __strong *obj1 = [[Foo alloc] init];
Foo __strong *obj2 = objc_retain(obj1);
Foo __unsafe_unretained *obj3 = obj1;
// do something with obj1, obj2 and obj3
objc_release(obj1);
objc_release(obj2);
return;
Innanzitutto osserviamo che obj1 e obj2 vengono entrambi marcati con il qualificatore strong. Perché? Perché non avendone specificati altri viene usato questo.
Essendo obj2 di tipo strong, ARC vi aggiunge una retain esplicita sull’oggetto puntato. In altre parole, sia obj1 sia obj2 diventano “proprietari” dell’oggetto da essi puntato. Naturalmente, dal momento che entrambi obj1 e obj2 sono diventati proprietari dell’oggetto, dovranno poi rilasciarlo quando non gli servirà più. Ma per fortuna non dobbiamo occuparcene noi: è proprio il compito di ARC, che inserisce le chiamate objc_release() appena prima di uscire dallo scope, ovvero appena prima della return. Ecco quindi svelato il significato del qualificatore strong: una variabile è marcata in questo modo quando è ARC a gestirne la memoria.
obj3 invece, essendo unsafe unretained, si comporta in modo diverso. Come possiamo osservare, obj3 non diventa “proprietario” dell’oggetto puntato, perché non fa su di esso una retain esplicita. In pratica, quando dichiariamo una variabile unsafe unretained stiamo dicendo ad ARC che non deve occuparsi lui della gestione della memoria. Attenzione però: quando entrambi obj1 e obj2 saranno stati rilasciati, obj3 continuerà a puntare ad un’area di memoria non valida. Poco grave in questo esempio specifico, ma è una cosa a cui conviene prestare molta attenzione. Per fortuna ci viene in aiuto il qualificatore weak. Usandolo al posto di unsafe unretained, ARC farebbe la “traduzione” in questo modo:
Foo __strong *obj1 = [[Foo alloc] init];
Foo __strong *obj2 = objc_retain(obj1);
Foo __weak *obj3 = obj1;
// do something with obj1, obj2 and obj3
objc_release(obj1);
objc_release(obj2);
obj3 = nil; // <---------
return;
Come possiamo vedere, dopo che entrambi obj1 e obj2 sono stati rilasciati, obj3 viene automaticamente posto a nil. Un bel vantaggio, anche se per poterne usufruire dobbiamo avere come target almeno iOS 5 o OS X Lion.
Rissumendo, quindi, se vogliamo che ARC si occupi lui della gestione della memoria dobbiamo dichiarare l'oggetto come strong (oppure non dire niente, dal momento che questo è il comportamento di default). Se invece vogliamo occuparcene noi, dobbiamo usare il qualificatore weak oppure, sui vecchi iOS 4 e Snow Leopard, unsafe unretained.
A questo punto ci è sicuramente sorto un dubbo: ma perché dovremmo usare weak o unsafe unretained? Perché non possiamo semplicemente demandare ad ARC il problema della gestione della memoria di tutte le variabili? Per via dei cosiddetti "retain cycle".
Retain cycle
Un "retain cycle" si presenta quando due oggetti si fanno (direttamente o indirettamente) una retain a vicenda. Questo comporta un memory leak, perché entrambi gli oggetti continuerebbero a "vivere" per tutta la durata dell'applicazione anche se non venissero più utilizzati da nessun'altro.
Il più semplice retain cycle che si può verificare è questo:

I nomi scelti per l'esempio non sono casuali. Un classico caso di retain cycle sono proprio i delegati. In tal caso, un oggetto (Foo nel nostro caso) mantiene un riferimento al suo delegato (FooDelegate). Se anche il delegato mantiene un riferimento di tipo strong a Foo (che è appunto il comportamento di default), nessuno dei due verrebbe mai rilasciato:

Come possiamo risolvere il problema? Usando le "weak reference":

Guardando il problema da un punto di vista più generale, le linee guida Apple consigliano che nel caso in cui due oggetti abbiano una relazione padre-figlio, il genitore debba mantenere un riferimento di tipo strong verso il figlio. Se il figlio necessita di un riferimento al padre questo deve invece essere di tipo weak. In tal modo scampiamo alla trappola del retain cycle.
Alcune volte i retain cycle sono più subdoli. È raro (ma non impossibile) avere catene di tre, quattro, cinque o più oggetti che puntano l'un l'altro in un circolo vizioso. Meglio starci attenti, quindi.
Come cambiano le proprietà
Quanto visto vale per gli oggetti e le variabili, ma anche per le proprietà esiste un approccio del tutto analogo.
Il vecchio codice:
@property (retain) FooDelegate *obj;
adesso dovrà diventare:
@property (strong) FooDelegate *obj;
Questo invece:
@property (assign) Foo *parentObj;
diventa:
@property (weak) Foo *parentObj;
Le regole più importanti
Alla luce di quanto detto, ecco le regole da seguire per poter compilare con ARC abilitato:
- alloc/init degli oggetti: la creazione degli oggetti risulta invariata ma non si possono più fare chiamate (dirette o indirette usando @selector) ai metodi retain/release/autorelease/retainCount.
- Metodi di deallocazione: vengono di norma creati automaticamente e, come sempre, non va mai chiamata direttamente la dealloc. Se fosse necessario rilasciare altre risorse (allocate ad esempio con le vecchie malloc), si può creare un metodo dealloc senza chiamare la [super dealloc], in quanto viene automaticamente aggiunta da ARC.
- Dichiarazione delle proprietà: utilizzare le parole chiave strong/weak in sostituzione di retain/copy/assign. strong lo usiamo quando vogliamo demandare ad ARC il problema della gestione della memoria. weak (o unsafe unretained sui vecchi iOS 4 e Snow Leopard) quando dobbiamo evitare dei retain cycle.
Migrazione
Per rendere un vecchio codice compatibile con ARC, Xcode 4.2 ci fornisce un ottimo strumento semi-automatizzato. Lo troviamo a partire dal menù Edit > Refactor > Convert to Objective-C ARC:

Il convertitore dapprima imposterà il compilatore Apple LLVM 3.0, che come dicevamo nell'introduzione è l'unico che attualmente supporta ARC. Questo passaggio tipicamente comporta il sorgere di errori o warning che dovremo correggere.
Successivamente il codice verrà reso compatibile con ARC, rimuovendo le chiamate ai metodi retain, release e autorelease e inserendo gli opportuni lifetime qualifier nella dichiarazione delle property. Inoltre, ogni NSAutoreleasePool verrà sostituito con il nuovo statement @autoreleasepool.
Coesistenza di codice ARC con codice non-ARC
Prima di concludere osserviamo che è possibile far coesistere codice ARC con codice non-ARC (interi framework, ma anche singoli file). Non entreremo nei dettagli in questo articolo, ma si tratta di una cosa molto utile per poter continuare ad utilizzare vecchie librerie stabili e consolidate negli anni senza doverle necessariamente migrare ad ARC.
Riferimenti
In rete purtroppo non vi sono ancora molte informazioni su ARC. I pochi articoli presenti sono estremamente tecnici. Ad ogni modo, a chi volesse approfondire l'argomento consigliamo questi:
Autori
Valerio Dutto e Marco Rocca sono i fondatori di Delite Studio S.r.l., società specializzata nella creazione di applicazioni native di alta qualità per OS X e iOS. Il loro sito Web è http://www.delitestudio.com.









13 Responses to “Introduzione ad ARC (Automatic Reference Counting)”
20 Ottobre 2011
Introduction to Objective-C Automatic Reference Counting (ARC) | Delite Studio[…] Our article on the Objective-C Automatic Reference Counting (in italian) is now live on devApp.it website. […]
20 Ottobre 2011
simoneWow, grazie mille della spiegazione molto chiara. Ero proprio curioso di capire come funziona questa nuova funzionalità e in rete non c’è in effetti ancora molto e certamente non così chiaro e in italiano!
20 Ottobre 2011
milonetmolto bello l’articolo e molto utile.. complimenti agli autori! ammetto che alcuni argomenti sono avanzati per un neofita come me ma devo dire che è cmq chiaro!
20 Ottobre 2011
AndreaComplimenti ottimo articolo….
Ne aproffito per chiedere una cosa è possibile vedere il codice che genera quando si ulitizza arc?
21 Ottobre 2011
Valerio DuttoGrazie a tutti per i complimenti!
Andrea, purtroppo ad oggi non mi risulta che vi sia alcuna opzione da passare al compilatore per vedere l’output di ARC. O meglio: nessuna opzione documentata.
A presto,
Valerio.
21 Ottobre 2011
AntScusate mi se scrivo qui e no nel forum sono con l’iPhone
21 Ottobre 2011
Giuseppe FratturaCiao ho letto l’articolo e lo trovo davvero interessante, con questa feature si porta il linguaggio ad essere facile come il java ma al tempo stesso potente come il c++.
21 Ottobre 2011
FrancescoComplimenti, articolo veramente completo.
23 Ottobre 2011
DaniloFinalmente!! Chi programma in tanti linguaggi come me finalmente può dire che qualcosa di troppo da ricordare ora è solo un “lontano ricordo” 😉
24 Ottobre 2011
NicholasOttimo articolo, complimenti è stato molto utile!
24 Ottobre 2011
LucaEro solito seguire questo tutorial per fare recensire la mia app utilizzando “Appirater” http://www.devapp.it/wordpress/t096-facciamoci-lasciare-una-recensione-in-app-store-dalla-nostra-applicazione-con-appirater.html
molto facile e comodo.. adesso ho provato a rimetterlo in una nuova app e facendo il refactor ottengo 1 errore, ripetuto 2 volte: Semantic Issue: ‘NSAutoreleasePool’ is unavailable: not available in automatic reference counting mode …. nel codice originale ho
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];e il refactor lo corregge in@autoreleasepoole da’ comunque errore… e poi stranamente compila.. ma non ho Appirater che funziona…..4 Novembre 2011
Andrea Cappellottoil refactor non sistema tutte le cose, anzi la maggior parte vanno sistemate a mano. Buona lezione, ma è solo uno spunto da approfondire, mancano i riferimenti a __briged. se volete un consiglio leggete questo http://www.raywenderlich.com/store/ios-5-by-tutorials
(penso k l’autore abbia preso spunto da qui)
9 Novembre 2011
Mattia CampoleseOttimo articolo. Una domanda, come comportarsi con il vecchio “copy” utilizzato nelle stringhe? (parlo di
@property (nonatomic, copy) NSString *testM)