Weak Reference — какво е, синтаксис и приложение в мобилната разработка

Автор: IT Sectr Публикувано: 2026-03-30 Време за четене: 9 мин

Weak Reference (слаба референция) — е референция към обект, която не увеличава неговия брояч на задържане в ARC. Според Apple Swift Language Guide, 2026, слабите референции се декларират с ключовата дума weak и винаги имат опционален тип. Когато обектът бъде освободен, всички weak референции към него автоматично се задават на nil, което предотвратява висящите указатели и прави слабите референции безопасен механизъм за прекъсване на retain cycle.

Основни

  • Weak Reference — референция, която не влияе на retain count на обекта; при освобождаване на обекта се нулира
  • Декларация — ключова дума weak преди var; типът винаги опционален (?)
  • Приложение — делегати, затваряния, parent-child отношения за прекъсване на retain cycle
  • Безопасност — автоматично задаване на nil след деалокация на обекта (zeroing weak)
  • Разлика от unowned — weak се нулира и е безопасен, unowned не се нулира и изисква гаранция за живот

Какво е Weak Reference?

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 в Swift и Objective-C

Нека разгледаме синтаксиса на деклариране на weak референции и в двата езика на екосистемата на Apple. Въпреки общия runtime, синтаксисът се различава, но семантиката е идентична.

Swift

В Swift weak референциите се декларират с ключовата дума weak преди var. Типът трябва винаги да бъде опционален (Type?), тъй като референцията може да бъде нулирана по всяко време. Константите (let) не могат да бъдат weak — само променливи.

swift
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

В Objective-C weak свойствата се декларират чрез атрибута __weak или модификатора weak в property:

objective-c
// 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 навсякъде води до прекомерна сложност и влошава четливостта. Нека разгледаме правилните сценарии за приложение.

Делегати (Delegate pattern)

Делегат — основният сценарий за weak. Обектът-собственик (напр. UITableView) държи strong референция към себе си, а делегатът (UIViewController) не трябва да притежава таблицата. Apple SDK гарантира, че всички delegate и dataSource са weak. За собствените си протоколи винаги използвайте weak var delegate.

Parent-Child с обратна връзка

Когато дъщерният обект трябва да реферира към родителя (напр. ChildViewController за достъп до координатор), използвайте weak референция. Родителят притежава детето (strong), детето наблюдава родителя (weak) — retain cycle изключен.

Асинхронни затваряния

Capture list [weak self] — стандартният начин за избягване на retain cycle в затваряния, съхранявани като свойства на клас. Ако self може да бъде освободен преди завършването на затварянето — weak self е задължителен.

СценарийWeakStrong
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 vs Unowned: сравнение и сценарии

И weak, и unowned не увеличават retain count, но се различават по поведение след деалокация на обекта. Изборът между тях е въпрос на гаранции за живот.

Разлики

Weak: автоматично се нулира (nil), типът винаги optional, изисква unwrap преди употреба. Безопасен — достъпът до nil не причинява crash.

Unowned: не се нулира, типът non-optional. Ако обектът е освободен, unowned референцията става висящ указател — достъпът до нея причинява runtime crash. Unowned предполага, че обектът живее не по-кратко от рефериращата страна.

Кога да изберете weak

Weak изберете, ако: обектът може да бъде деалокиран по всяко време (делегат след затваряне на екран), не контролирате живота на обекта или се съмнявате в гаранциите. Weak — универсален безопасен избор.

Кога да изберете unowned

Unowned изберете, ако: обектът гарантирано не може да бъде освободен преди рефериращия (напр. Customer → CreditCard, където картата не съществува без клиент). Unowned предоставя non-optional API без unwrap, което е по-удобно в кода.

swift
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) може да бъде затворен и освободен по всяко време.

Ограничения на weak референциите и капани

Слабите референции — мощен инструмент, но имат ограничения, които е важно да разберете за правилно приложение в iOS разработката.

Производителност на weak

Слабите референции са по-бавни от strong: при всеки достъп runtime проверява дали обектът е освободен (lookup в weak таблицата). В огромното мнозинство от сценарии разликата е незабележима, но в горещи цикли с милиони достъпи weak може да бъде тясно място. За сценарии с високо натоварване използвайте strong и реорганизирайте архитектурата.

Weak не се прилага към типове стойности

Struct, enum, tuple — типове стойности, които не участват в ARC. Опитът за деклариране на weak struct води до грешка при компилация. За съхраняване на слаба референция към тип стойност използвайте обвивка в class-тип или затваряне.

Weak в многонишковост

Zeroing weak е thread-safe: ако обектът се освобождава на една нишка, weak референцията се нулира на всички нишки атомарно. Въпреки това, интервалът между четене на weak референция и нейното използване може да доведе до състезателно условие — обектът се освобождава между получаването на weak референцията и нейното използване. Решение: strong-захващане на слабата референция в локална променлива.

swift
// Състезателно условие с 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.

UIView и weak outlet

IBOutlet в Interface Builder трябва да бъдат weak, тъй като йерархията на изгледите вече държи strong референция към subview. Дублирането на strong референция в контролера не създава retain cycle, но е излишно. Слабата референция към outlet — препоръка на Apple, въпреки че много разработчици използват strong за опростяване на кода.

Често задавани въпроси

Може ли weak референция да сочи към обект, който все още не е създаден?

Не, weak може да сочи само към съществуващ обект или nil. При създаване на нов обект първо получавате strong референция (чрез инициализатор) и едва след това можете да присвоите слаба референция. weak nil в началото — нормално състояние.

Защо weak работи само с class-types?

Weak се основава на ARC, който управлява само референтни типове (класове). Типовете стойности (struct, enum) се копират при присвояване и нямат retain count. За слаба връзка на типове стойности използвайте затваряния или обвивки в class с weak свойство.

Как weak влияе на производителността в цикъл?

Всеки достъп до weak референция изпълнява lookup в runtime таблицата. В цикъл с милиони итерации това може да бъде 2–5 пъти по-бавно от strong референция. За горещи пътища копирайте weak в локална strong променлива преди цикъла.

Кога weak референция може неочаквано да стане nil?

Когато всички strong референции към обекта бъдат загубени — в края на обхвата, при повторно задаване на свойство, при затваряне на екран. В многонишкова среда това може да се случи между два реда код. Винаги проверявайте weak чрез guard let или if let.

Как се различава weak от __weak в Objective-C?

Семантично идентични: и двете предоставят zeroing weak. Разлики: Swift изисква optional тип и var, Objective-C използва модификатор на property. Objective-C също поддържа __unsafe_unretained — слаба референция без zeroing (риск от висящ указател).

Резюме

  • Weak Reference — невладееща референция, която не увеличава retain count и автоматично се нулира при деалокация
  • Синтаксисweak var + опционален тип; само class-types и протоколи AnyObject
  • Zeroing weak — runtime нулира всички weak референции към освободен обект, предотвратявайки висящи указатели
  • Сценарии — делегати, parent-child с обратна връзка, асинхронни затваряния ([weak self])
  • Weak vs Unowned — weak се нулира (безопасно), unowned не се нулира (риск от crash, но non-optional)
  • Производителност — weak по-бавен от strong поради lookup в runtime таблицата; за горещи пътища копирайте в strong
  • Препоръка — ако не сте сигурни в гаранциите за живот — изберете weak

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също