Weak Reference (referință slabă) — este o referință la un obiect care nu crește contorul său de reținere în ARC. Conform Apple Swift Language Guide, 2026, referințele weak se declară cu cuvântul cheie weak și au întotdeauna un tip opțional. Când obiectul este eliberat, toate referințele weak către el sunt setate automat la nil, ceea ce previne pointerii dangling și face referințele slabe un mecanism sigur pentru ruperea retain cycle.
Principalele
weak înainte de var; tipul întotdeauna opțional (?)Weak Reference — este o referință neposesivă la un obiect în ARC (Automatic Reference Counting). Spre deosebire de referința strong, care crește retain count-ul obiectului și garantează viața acestuia, referința weak permite obiectului să fie eliberat chiar dacă mai există referințe către el. După eliberare, referința weak este setată automat la nil — aceasta se numește zeroing weak.
Zeroing weak — caracteristica cheie a runtime-ului Swift și Objective-C. Când contorul de referințe al obiectului ajunge la zero și obiectul este dezalocat, runtime-ul parcurge toate referințele weak către acest obiect (stocate într-un tabel weak special) și le setează la nil. Aceasta garantează că accesările la memoria eliberată (use-after-free) sunt imposibile prin referințe weak — orice citire returnează nil.
Conform Apple WWDC 2012 Session 406, referințele zeroing weak au eliminat o întreagă clasă de bug-uri de crash asociate cu pointerii dangling, care erau răspândite în gestionarea manuală a memoriei (MRR). În MRR referințele slabe existau doar ca __unsafe_unretained — ele nu se anulau, iar accesarea unui obiect eliberat ducea la EXC_BAD_ACCESS.
Să examinăm sintaxa declarării referințelor weak în ambele limbi ale ecosistemului Apple. În ciuda runtime-ului comun, sintaxa diferă, dar semantica este identică.
În Swift referințele weak se declară cu cuvântul cheie weak înainte de var. Tipul trebuie să fie întotdeauna opțional (Type?), deoarece referința se poate anula în orice moment. Constantele (let) nu pot fi weak — doar variabilele.
class ViewController: UIViewController {
// proprietăți weak: doar var, doar optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ closure-urile weak nu stochează
// ⬆️ Eroare: weak se poate aplica doar class-types, nu la closure
}
Important: weak se aplică doar instanțelor de clase (class-types), AnyObject și protocoalelor moștenite de la AnyObject. Struct, enum și closure nu pot fi weak — ele sunt tipuri de valoare și nu participă la ARC.
În Objective-C proprietățile weak se declară prin atributul __weak sau modificatorul weak în property:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Variabilă weak locală
__weak MyObject *weakRef = someStrongObject;
Runtime-ul Objective-C oferă de asemenea zeroing weak, dar suplimentar blochează utilizarea weak cu structuri C și unele obiecte Core Foundation. Pentru ele se aplică __unsafe_unretained — fără zeroing.
Referințele slabe — nu sunt o soluție universală, ci un instrument pentru scenarii specifice. Folosirea weak peste tot duce la complexitate excesivă și degradează lizibilitatea. Să examinăm scenariile corecte de aplicare.
Delegate — scenariul principal pentru weak. Obiectul-proprietar (de exemplu, UITableView) deține o referință strong către sine, iar delegatul (UIViewController) nu trebuie să posede tabelul. Apple SDK garantează că toți delegate și dataSource sunt weak. Pentru propriile protocoale folosește întotdeauna weak var delegate.
Când obiectul copil trebuie să se refere la părinte (de exemplu, ChildViewController pentru acces la coordinator), folosește o referință weak. Părintele posedă copilul (strong), copilul observă părintele (weak) — retain cycle exclus.
Capture list [weak self] — modalitatea standard de a evita retain cycle în closure-uri stocate ca proprietăți ale clasei. Dacă self poate fi eliberat înainte de finalizarea closure-ului — weak self este obligatoriu.
| Scenariu | Weak | Strong |
|---|---|---|
| Delegate | ✅ Întotdeauna weak | ❌ Retain cycle |
| Parent → Child | ❌ Nu e necesar (părintele trebuie să posede) | ✅ Strong |
| Child → Parent | ✅ Weak | ❌ Retain cycle |
| Callback asincron | ✅ [weak self] | ❌ Risc retain cycle |
| Legătură puternică (owned) | ❌ unowned | ✅ Strong |
Regula generală: dacă obiectul A posedă B (A → B strong), atunci B → A trebuie să fie weak sau unowned. Direcția referințelor strong trebuie să fie întotdeauna de la proprietar la subordonat.
Atât weak cât și unowned nu cresc retain count-ul, dar se comportă diferit după dezalocarea obiectului. Alegerea dintre ele este o chestiune de garanții de viață.
Weak: se anulează automat (nil), tipul întotdeauna optional, necesită unwrap înainte de utilizare. Sigur — accesarea nil nu provoacă crash.
Unowned: nu se anulează, tipul non-optional. Dacă obiectul a fost eliberat, referința unowned devine un pointer dangling — accesarea ei provoacă runtime crash. Unowned presupune că obiectul trăiește cel puțin cât partea care se referă la el.
Weak alege dacă: obiectul poate fi dezalocat în orice moment (delegat după închiderea ecranului), nu controlezi durata de viață a obiectului sau ai îndoieli privind garanțiile. Weak — alegerea sigură universală.
Unowned alege dacă: obiectul garantat nu poate fi eliberat înaintea celui care se referă la el (de exemplu, Customer → CreditCard, unde cardul nu există fără client). Unowned oferă API non-optional fără unwrap, ceea ce este mai comod în cod.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Legătură puternică: Order posedă Item
func addItem(name: String) {
let item = Item(name: name, order: self)
items.append(item)
}
}
class Item {
let name: String
unowned let order: Order // ✅ unowned — Item nu trăiește fără Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Exemplu cu weak: delegat fără garanție de viață
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — delegatul poate pleca
}
În exemplu, Item folosește unowned, deoarece elementul comenzii nu poate exista fără comanda însăși — garanția de viață este de fier. NetworkService folosește weak, deoarece delegatul (de exemplu, ViewController) poate fi închis și eliberat în orice moment.
Referințele slabe — un instrument puternic, dar au limitări care trebuie înțelese pentru aplicarea corectă în dezvoltarea iOS.
Referințele slabe sunt mai lente decât strong: la fiecare acces runtime-ul verifică dacă obiectul a fost eliberat (lookup în tabela weak). În marea majoritate a scenariilor diferența este insesizabilă, dar în bucle fierbinți cu milioane de accesări weak poate fi un blocaj. Pentru scenarii cu încărcare mare folosește strong și reorganizează arhitectura.
Struct, enum, tuple — tipuri de valoare care nu participă la ARC. Încercarea de a declara weak struct duce la eroare de compilare. Pentru stocarea unei referințe slabe la un tip de valoare folosește un wrapper în class-type sau un closure.
Zeroing weak este thread-safe: dacă obiectul este eliberat pe un fir, referința weak este anulată pe toate firele atomic. Totuși, intervalul dintre citirea referinței weak și dereferențiere poate duce la o condiție de cursă — obiectul este eliberat între obținerea referinței weak și utilizarea ei. Soluția: strong-capturarea referinței slabe într-o variabilă locală.
// Race condition cu weak în multi-threading
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf poate fi nil între verificare și utilizare
if weakSelf != nil {
weakSelf!.doSomething() // CRASH dacă a devenit nil
}
}
}
// ✅ Corecție: capturare strong pe durata utilizării
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — referință strong locală
}
}
În varianta sigură, weak self este capturat, apoi imediat desfăcut într-o variabilă strong locală strongSelf. Dacă self este încă viu — va rămâne viu pe durata executării blocului. Dacă nu — guard se activează și codul nu se execută. Acest idiom — modelul standard pentru closure-uri asincrone în Swift.
IBOutlet în Interface Builder trebuie să fie weak, deoarece ierarhia de view deține deja o referință strong la subview. Duplicarea referinței strong în controler nu creează retain cycle, dar este redundantă. Referința slabă la outlet — recomandarea Apple, deși mulți dezvoltatori folosesc strong pentru simplificarea codului.
Întrebări frecvente
Nu, weak poate indica doar către un obiect existent sau nil. La crearea unui obiect nou obținem mai întâi o referință strong (prin inițializator), și doar apoi putem atribui o referință slabă. weak nil la început — o stare normală.
Weak se bazează pe ARC, care gestionează doar tipurile de referință (clase). Tipurile de valoare (struct, enum) se copiază la atribuire și nu au retain count. Pentru legătura slabă a tipurilor de valoare folosește closure-uri sau wrapper în class cu proprietate weak.
Fiecare acces la o referință weak execută un lookup în tabela runtime. Într-o buclă cu milioane de iterații acesta poate fi de 2–5 ori mai lent decât o referință strong. Pentru căi fierbinți copiază weak într-o variabilă strong locală înainte de buclă.
Când toate referințele strong către obiect sunt pierdute — la sfârșitul scopului, la resetarea proprietății, la închiderea ecranului. În mediul multi-thread aceasta se poate întâmpla între două linii de cod. Verifică întotdeauna weak prin guard let sau if let.
Semantic identic: ambele oferă zeroing weak. Diferențe: Swift necesită tip optional și var, Objective-C folosește modificatorul property. Objective-C suportă de asemenea __unsafe_unretained — referință slabă fără zeroing (risc de pointer dangling).
Rezumat
weak var + tip optional; doar class-types și protocoale AnyObjectVom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și