Weak Reference (слаба референция) — е референция към обект, която не увеличава неговия брояч на задържане в ARC. Според Apple Swift Language Guide, 2026, слабите референции се декларират с ключовата дума weak и винаги имат опционален тип. Когато обектът бъде освободен, всички weak референции към него автоматично се задават на nil, което предотвратява висящите указатели и прави слабите референции безопасен механизъм за прекъсване на retain cycle.
Основни
weak преди var; типът винаги опционален (?)Weak Reference — е невладееща референция към обект в ARC (Automatic Reference Counting). За разлика от strong референцията, която увеличава retain count на обекта и гарантира неговия живот, weak референцията позволява на обекта да бъде освободен, дори ако все още има референции към него. След освобождаване weak референцията автоматично се задава на nil — това се нарича zeroing weak.
Zeroing weak — ключова характеристика на Swift и Objective-C runtime. Когато броячът на референции на обекта достигне нула и обектът бъде деалокиран, runtime обхожда всички weak референции към този обект (съхранявани в специална weak таблица) и ги задава на nil. Това гарантира, че достъпът до освободена памет (use-after-free) е невъзможен чрез weak референции — всяко четене връща nil.
Според Apple WWDC 2012 Session 406, zeroing weak референциите елиминираха цял клас crash грешки, свързани с висящи указатели (dangling pointers), които бяха разпространени в ръчното управление на паметта (MRR). В MRR слабите референции съществуваха само като __unsafe_unretained — те не се нулираха и достъпът до освободен обект водеше до EXC_BAD_ACCESS.
Нека разгледаме синтаксиса на деклариране на weak референции и в двата езика на екосистемата на Apple. Въпреки общия runtime, синтаксисът се различава, но семантиката е идентична.
В Swift weak референциите се декларират с ключовата дума weak преди var. Типът трябва винаги да бъде опционален (Type?), тъй като референцията може да бъде нулирана по всяко време. Константите (let) не могат да бъдат weak — само променливи.
class ViewController: UIViewController {
// weak-свойства: само var, само optional
weak var delegate: ViewControllerDelegate?
weak var parentView: UIView?
weak var completionHandler: ((Bool) -> Void)? // ⚠️ затварянията weak не съхраняват
// ⬆️ Грешка: weak може да се приложи само към class-types, не към closure
}
Важно: weak се прилага само към инстанции на класове (class-types), AnyObject и протоколи, наследени от AnyObject. Struct, enum и closure не могат да бъдат weak — те са типове стойности и не участват в ARC.
В Objective-C weak свойствата се декларират чрез атрибута __weak или модификатора weak в property:
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end
// Локална weak-променлива
__weak MyObject *weakRef = someStrongObject;
Objective-C runtime също предоставя zeroing weak, но допълнително блокира използването на weak с C-структури и някои Core Foundation обекти. За тях се прилага __unsafe_unretained — без zeroing.
Слабите референции — не са универсално решение, а инструмент за конкретни сценарии. Използването на weak навсякъде води до прекомерна сложност и влошава четливостта. Нека разгледаме правилните сценарии за приложение.
Делегат — основният сценарий за weak. Обектът-собственик (напр. UITableView) държи strong референция към себе си, а делегатът (UIViewController) не трябва да притежава таблицата. Apple SDK гарантира, че всички delegate и dataSource са weak. За собствените си протоколи винаги използвайте weak var delegate.
Когато дъщерният обект трябва да реферира към родителя (напр. ChildViewController за достъп до координатор), използвайте weak референция. Родителят притежава детето (strong), детето наблюдава родителя (weak) — retain cycle изключен.
Capture list [weak self] — стандартният начин за избягване на retain cycle в затваряния, съхранявани като свойства на клас. Ако self може да бъде освободен преди завършването на затварянето — weak self е задължителен.
| Сценарий | Weak | Strong |
|---|---|---|
| Delegate | ✅ Винаги weak | ❌ Retain cycle |
| Parent → Child | ❌ Не е нужно (родителят трябва да притежава) | ✅ Strong |
| Child → Parent | ✅ Weak | ❌ Retain cycle |
| Асинхронен callback | ✅ [weak self] | ❌ Риск от retain cycle |
| Силна връзка (owned) | ❌ unowned | ✅ Strong |
Общо правило: ако обект A притежава B (A → B strong), то B → A трябва да бъде weak или unowned. Посоката на strong референциите винаги трябва да бъде от собственика към подчинения.
И weak, и unowned не увеличават retain count, но се различават по поведение след деалокация на обекта. Изборът между тях е въпрос на гаранции за живот.
Weak: автоматично се нулира (nil), типът винаги optional, изисква unwrap преди употреба. Безопасен — достъпът до nil не причинява crash.
Unowned: не се нулира, типът non-optional. Ако обектът е освободен, unowned референцията става висящ указател — достъпът до нея причинява runtime crash. Unowned предполага, че обектът живее не по-кратко от рефериращата страна.
Weak изберете, ако: обектът може да бъде деалокиран по всяко време (делегат след затваряне на екран), не контролирате живота на обекта или се съмнявате в гаранциите. Weak — универсален безопасен избор.
Unowned изберете, ако: обектът гарантирано не може да бъде освободен преди рефериращия (напр. Customer → CreditCard, където картата не съществува без клиент). Unowned предоставя non-optional API без unwrap, което е по-удобно в кода.
class Order {
let id: Int
var items: [Item] = []
init(id: Int) { self.id = id }
// Силна връзка: Order притежава 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 не живее без Order
init(name: String, order: Order) {
self.name = name
self.order = order
}
}
// Пример с weak: делегат без гаранция за живот
protocol NetworkServiceDelegate: AnyObject {
func didReceiveResponse(data: Data)
}
class NetworkService {
weak var delegate: NetworkServiceDelegate? // ✅ weak — делегат може да си отиде
}
В примера Item използва unowned, тъй като елементът на поръчка не може да съществува без самата поръчка — гаранцията за живот е желязна. NetworkService използва weak, тъй като делегатът (напр. ViewController) може да бъде затворен и освободен по всяко време.
Слабите референции — мощен инструмент, но имат ограничения, които е важно да разберете за правилно приложение в iOS разработката.
Слабите референции са по-бавни от strong: при всеки достъп runtime проверява дали обектът е освободен (lookup в weak таблицата). В огромното мнозинство от сценарии разликата е незабележима, но в горещи цикли с милиони достъпи weak може да бъде тясно място. За сценарии с високо натоварване използвайте strong и реорганизирайте архитектурата.
Struct, enum, tuple — типове стойности, които не участват в ARC. Опитът за деклариране на weak struct води до грешка при компилация. За съхраняване на слаба референция към тип стойност използвайте обвивка в class-тип или затваряне.
Zeroing weak е thread-safe: ако обектът се освобождава на една нишка, weak референцията се нулира на всички нишки атомарно. Въпреки това, интервалът между четене на weak референция и нейното използване може да доведе до състезателно условие — обектът се освобождава между получаването на weak референцията и нейното използване. Решение: strong-захващане на слабата референция в локална променлива.
// Състезателно условие с weak в многонишковост
func performAsync() {
weak var weakSelf = self
queue.async {
// ⚠️ weakSelf може да бъде nil между проверка и употреба
if weakSelf != nil {
weakSelf!.doSomething() // CRASH ако е станал nil
}
}
}
// ✅ Поправка: силно захващане за времето на употреба
func performAsyncSafe() {
queue.async { [weak self] in
guard let strongSelf = self else { return }
strongSelf.doSomething() // strongSelf — локална strong референция
}
}
В безопасния вариант weak self се захваща, след което незабавно се разопакова в локална strong променлива strongSelf. Ако self все още е жив — ще остане жив по време на изпълнение на блока. Ако не — guard се активира и кодът не се изпълнява. Този идиом — стандартният модел за асинхронни затваряния в Swift.
IBOutlet в Interface Builder трябва да бъдат weak, тъй като йерархията на изгледите вече държи strong референция към subview. Дублирането на strong референция в контролера не създава retain cycle, но е излишно. Слабата референция към outlet — препоръка на Apple, въпреки че много разработчици използват strong за опростяване на кода.
Често задавани въпроси
Не, weak може да сочи само към съществуващ обект или nil. При създаване на нов обект първо получавате strong референция (чрез инициализатор) и едва след това можете да присвоите слаба референция. weak nil в началото — нормално състояние.
Weak се основава на ARC, който управлява само референтни типове (класове). Типовете стойности (struct, enum) се копират при присвояване и нямат retain count. За слаба връзка на типове стойности използвайте затваряния или обвивки в class с weak свойство.
Всеки достъп до weak референция изпълнява lookup в runtime таблицата. В цикъл с милиони итерации това може да бъде 2–5 пъти по-бавно от strong референция. За горещи пътища копирайте weak в локална strong променлива преди цикъла.
Когато всички strong референции към обекта бъдат загубени — в края на обхвата, при повторно задаване на свойство, при затваряне на екран. В многонишкова среда това може да се случи между два реда код. Винаги проверявайте weak чрез guard let или if let.
Семантично идентични: и двете предоставят zeroing weak. Разлики: Swift изисква optional тип и var, Objective-C използва модификатор на property. Objective-C също поддържа __unsafe_unretained — слаба референция без zeroing (риск от висящ указател).
Резюме
weak var + опционален тип; само class-types и протоколи AnyObjectЩе разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също