Weak Reference — що це, синтаксис та застосування в мобільній розробці

Автор: IT Sectr Опубліковано: 2026-03-30 Час читання: 9 хв

Weak Reference (слабке посилання) — це посилання на об'єкт, яке не збільшує його лічильник утримання в ARC. Згідно з Apple Swift Language Guide, 2026, слабкі посилання оголошуються з ключовим словом weak і завжди мають опціональний тип. Коли об'єкт звільняється, всі слабкі посилання на нього автоматично встановлюються в nil, що запобігає висячим покажчикам і робить слабкі посилання безпечним механізмом для розриву циклів утримання.

Головне

  • Weak Reference — посилання, що не впливає на retain count об'єкта; при звільненні об'єкта обнуляється
  • Оголошення — ключове слово weak перед var; тип завжди опціональний (?)
  • Застосування — делегати, замикання, відносини батько-дитина для розриву циклів утримання
  • Безпека — автоматичне встановлення в nil після деалокації об'єкта (zeroing weak)
  • Відмінність від unowned — weak обнуляється і безпечний, unowned не обнуляється і потребує гарантій часу життя

Що таке Weak Reference?

Weak Reference — це неволодіюче посилання на об'єкт в ARC (Automatic Reference Counting). На відміну від сильного посилання, яке збільшує retain count об'єкта та гарантує його життя, слабке посилання дозволяє об'єкту звільнитися, навіть якщо на нього все ще посилаються. Після звільнення слабке посилання автоматично встановлюється в nil — це називається zeroing weak.

Zeroing weak — ключова особливість середовища виконання Swift та Objective-C. Коли лічильник посилань об'єкта досягає нуля та об'єкт деалокується, середовище виконання обходить всі слабкі посилання на цей об'єкт (що зберігаються в спеціальній слабкій таблиці) та встановлює їх у nil. Це гарантує, що доступ до звільненої пам'яті (use-after-free) неможливий через слабкі посилання — будь-яке читання повертає nil.

Згідно з Apple WWDC 2012 Session 406, zeroing weak посилання усунули цілий клас помилок crash, пов'язаних з висячими покажчиками (dangling pointers), які були поширені в ручному керуванні пам'яттю (MRR). У MRR слабкі посилання існували лише як __unsafe_unretained — вони не обнулялися, і звернення до звільненого об'єкта призводило до EXC_BAD_ACCESS.

Синтаксис weak у Swift та Objective-C

Розглянемо синтаксис оголошення слабких посилань в обох мовах екосистеми Apple. Незважаючи на спільне середовище виконання, синтаксис відрізняється, але семантика ідентична.

Swift

У Swift слабкі посилання оголошуються з ключовим словом 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, не до closure
}

Важливо: weak застосовний лише до екземплярів класів (типів class), AnyObject та протоколів, успадкованих від AnyObject. Struct, enum та замикання не можуть бути weak — вони є типами значень і не беруть участі в ARC.

Objective-C

У Objective-C слабкі властивості оголошуються через атрибут __weak або модифікатор weak в оголошеннях властивостей:

objective-c
// Objective-C: weak property
@interface MyViewController : UIViewController
@property (weak, nonatomic) id<MyDelegate> delegate;
@end

// Локальна weak-змінна
__weak MyObject *weakRef = someStrongObject;

Середовище виконання Objective-C також надає zeroing weak, але додатково блокує використання weak з C-структурами та деякими об'єктами Core Foundation. Для них застосовується __unsafe_unretained — без zeroing.

Коли застосовувати слабкі посилання

Слабкі посилання — не універсальне рішення, а інструмент для конкретних сценаріїв. Використання weak скрізь призводить до надмірної складності та погіршує читабельність. Розглянемо правильні сценарії застосування.

Делегати (патерн Delegate)

Делегати — основний сценарій для weak. Об'єкт-власник (наприклад, UITableView) тримає сильне посилання на себе, а делегат (UIViewController) не повинен володіти таблицею. Apple SDK гарантує, що всі delegate та dataSource — weak. Для власних протоколів завжди використовуйте weak var delegate.

Батько-Дитина зі зворотним зв'язком

Коли дочірній об'єкт повинен посилатися на батька (наприклад, ChildViewController для доступу до координатора), використовуйте weak-посилання. Батько володіє дитиною (strong), дитина спостерігає за батьком (weak) — цикл утримання виключено.

Асинхронні замикання

Capture list [weak self] — стандартний спосіб уникнути циклів утримання в замиканнях, що зберігаються як властивості класу. Якщо self може бути звільнено до завершення замикання — weak self обов'язковий.

СценарійWeakStrong
Delegate✅ Завжди weak❌ Цикл утримання
Батько → Дитина❌ Не потрібно (батько повинен володіти)✅ Strong
Дитина → Батько✅ Weak❌ Цикл утримання
Асинхронний callback✅ [weak self]❌ Ризик циклу утримання
Сильний зв'язок (owned)❌ unowned✅ Strong

Загальне правило: якщо об'єкт A володіє B (A → B strong), то B → A повинен бути weak або unowned. Напрямок сильних посилань завжди повинен бути від власника до підлеглого.

Weak vs Unowned: порівняння та сценарії

І weak, і unowned не збільшують retain count, але відрізняються поведінкою після деалокації об'єкта. Вибір між ними — питання гарантій часу життя.

Відмінності

Weak: автоматично обнуляється (nil), тип завжди опціональний, потребує unwrap перед використанням. Безпечний — звернення до nil не викликає crash.

Unowned: не обнуляється, тип non-optional. Якщо об'єкт звільнено, unowned-посилання стає висячим покажчиком — звернення до нього викликає 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) може бути закритий і звільнений у будь-який момент.

Обмеження слабких посилань та підводні камені

Слабкі посилання — потужний інструмент, але вони мають обмеження, які важливо розуміти для коректного застосування в iOS-розробці.

Продуктивність weak

Слабкі посилання повільніші за сильні: при кожному доступі середовище виконання перевіряє, чи не звільнено об'єкт (lookup у слабкій таблиці). У переважній більшості сценаріїв різниця непомітна, але в гарячих циклах з мільйонами звернень weak може бути вузьким місцем. Для високонавантажених сценаріїв використовуйте strong та реорганізуйте архітектуру.

Weak не застосовний до типів значень

Struct, enum, tuple — типи значень, що не беруть участі в ARC. Спроба оголосити weak struct призводить до помилки компіляції. Для зберігання слабкого посилання на тип значення використовуйте обгортку в типі class або замикання.

Weak у багатопоточності

Zeroing weak потокобезпечний: якщо об'єкт звільняється в одному потоці, слабке посилання обнуляється в усіх потоках атомарно. Однак проміжок між читанням слабкого посилання та розіменуванням може призвести до стану гонки — об'єкт звільняється між отриманням слабкого посилання та його використанням. Рішення: сильне захоплення слабкого посилання в локальну змінну.

swift
// Race condition з 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 — локальне сильне посилання
    }
}

У безпечному варіанті weak self захоплюється, потім одразу розгортається в локальну сильну змінну strongSelf. Якщо self ще живий — він залишиться живим на час виконання блоку. Якщо ні — спрацьовує guard, і код не виконується. Цей ідіома — стандартний патерн для асинхронних замикань у Swift.

UIView та weak outlet

IBOutlet в Interface Builder повинні бути weak, оскільки ієрархія view вже тримає сильне посилання на subview. Дублювання сильного посилання в контролері не створює циклу утримання, але є надлишковим. Слабке посилання на outlet — рекомендація Apple, хоча багато розробників використовують strong для спрощення коду.

Поширені запитання

Чи може weak-посилання вказувати на об'єкт, який ще не створено?

Ні, weak може вказувати лише на існуючий об'єкт або nil. При створенні нового об'єкта ви спочатку отримуєте сильне посилання (через ініціалізатор), і лише потім можете присвоїти слабке посилання. weak nil на початку — нормальний стан.

Чому weak працює лише з типами class?

Weak заснований на ARC, який керує лише референсними типами (класи). Типи значень (struct, enum) копіюються при присвоєнні та не мають retain count. Для слабкого зв'язку типів значень використовуйте замикання або обгортки в class з weak-властивістю.

Як weak впливає на продуктивність у циклі?

Кожен доступ до weak-посилання виконує lookup у таблиці середовища виконання. У циклі з мільйонами ітерацій це може бути в 2–5 разів повільніше за сильне посилання. Для гарячих шляхів скопіюйте weak у локальну сильну змінну перед циклом.

Коли weak-посилання може стати nil несподівано?

Коли всі сильні посилання на об'єкт втрачено — наприкінці області видимості, при перевстановленні властивості, при закритті екрана. У багатопотоковому середовищі це може статися між двома рядками коду. Завжди перевіряйте weak через guard let або if let.

Чим weak відрізняється від __weak в Objective-C?

Семантично ідентично: обидва надають zeroing weak. Відмінності: Swift вимагає опціональний тип та var, Objective-C використовує модифікатор властивості. Objective-C також підтримує __unsafe_unretained — слабке посилання без zeroing (ризик висячого покажчика).

Підсумки

  • Weak Reference — неволодіюче посилання, що не збільшує retain count і автоматично обнуляється при деалокації
  • Синтаксисweak var + опціональний тип; лише типи class та протоколи AnyObject
  • Zeroing weak — середовище виконання обнуляє всі weak-посилання на звільнений об'єкт, запобігаючи висячим покажчикам
  • Сценарії — делегати, батько-дитина зі зворотним зв'язком, асинхронні замикання ([weak self])
  • Weak vs Unowned — weak обнуляється (безпечно), unowned не обнуляється (ризик crash, але non-optional)
  • Продуктивність — weak повільніше strong через lookup у таблиці середовища виконання; для гарячих шляхів копіюйте в strong
  • Рекомендація — якщо не впевнені в гарантіях часу життя — вибирайте weak

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також