Storia della domanda:
Con lo sviluppo di Objective-C e l'emergere di Swift, Apple ha affrontato la questione della gestione automatica e sicura della memoria delle applicazioni senza che il programmatore debba lavorare esplicitamente con i comandi retain/release. Swift utilizza l'Automatic Reference Counting (ARC) che consente di monitorare il numero di riferimenti agli oggetti-classi e di liberare automaticamente la memoria quando l'ultimo proprietario scompare.
Problema:
Poiché Swift è un linguaggio multi-paradigma, opera sia con tipi valore (struct, enum) che con tipi riferimento (class). Per questi ultimi è importante che la memoria venga liberata in tempo, altrimenti si possono verificare perdite di memoria. Situazioni complesse si presentano con i retain cycle (riferimenti ciclici), quando due oggetti si riferiscono l'uno all'altro, impedendo ad ARC di liberare la memoria.
Soluzione:
Swift applica ARC solo agli oggetti delle classi. Quando il numero di riferimenti diventa 0, la memoria viene liberata. Per risolvere i problemi di retain cycle si utilizzano le parole chiave weak/unowned.
Esempio di codice:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name) sta venendo deallocato") } } class Pet { var owner: Person? init() {} deinit { print("Pet sta venendo deallocato") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // Entrambi gli oggetti NON vengono liberati, si verifica un retain cycle
Caratteristiche principali:
È possibile applicare ARC ai tipi valore?
No, ARC traccia solo i riferimenti agli oggetti delle classi. Le strutture e gli enum vengono passati per valore, e Swift libera automaticamente la loro memoria (di solito al termine della loro visibilità).
Cosa accade se non si utilizzano weak/unowned nelle proprietà di due oggetti che si riferiscono reciprocamente?
Si verifica un riferimento ciclico (retain cycle), per cui ARC non libererà mai la memoria per questi oggetti. Tale codice porta a una memory leak — si deve sempre utilizzare weak (o unowned) per almeno un lato di tale relazione.
A cosa serve unowned, se c'è weak?
unowned è necessario nei casi in cui il riferimento deve sempre esistere durante la vita di un altro oggetto e non deve mai diventare nil. weak è un riferimento nullo, unowned è non nullo, ma non incrementa il contatore retain.
Esempio di codice:
class A { var b: B? } class B { unowned var a: A // non è mai nil durante la vita di B }
Due controller contengono riferimenti forti l'uno per l'altro: uno tramite la proprietà delegato, l'altro tramite un array di controllori figli. Il creatore non utilizza weak per il delegato.
Pro: Gli errori non sono immediatamente visibili, tutto "funziona" a prima vista.
Contro: Con l'aumento delle dimensioni dell'applicazione, la memoria non viene liberata, si verificano perdite; lavorando con una memoria limitata si possono verificare crash.
Il delegato e l'osservatore di eventi sono impostati come weak, l'array di controllori viene pulito man mano che i controllori scompaiono. Tutto è documentato in modo ordinato.
Pro: Nessuna perdita, tutti gli oggetti vengono liberati tempestivamente, non ci sono perdite di prestazioni.
Contro: Possibile perdita del delegato in modo inaspettato (se è dimenticato un riferimento forte da qualche parte); richiede attenzione nella progettazione dell'applicazione.