Weak Reference (слабке посилання) — це посилання на об'єкт, яке не збільшує його лічильник утримання в ARC. Згідно з Apple Swift Language Guide, 2026, слабкі посилання оголошуються з ключовим словом weak і завжди мають опціональний тип. Коли об'єкт звільняється, всі слабкі посилання на нього автоматично встановлюються в nil, що запобігає висячим покажчикам і робить слабкі посилання безпечним механізмом для розриву циклів утримання.
Головне
weak перед var; тип завжди опціональний (?)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.
Розглянемо синтаксис оголошення слабких посилань в обох мовах екосистеми Apple. Незважаючи на спільне середовище виконання, синтаксис відрізняється, але семантика ідентична.
У Swift слабкі посилання оголошуються з ключовим словом 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, не до closure
}
Важливо: weak застосовний лише до екземплярів класів (типів class), AnyObject та протоколів, успадкованих від AnyObject. Struct, enum та замикання не можуть бути weak — вони є типами значень і не беруть участі в ARC.
У Objective-C слабкі властивості оголошуються через атрибут __weak або модифікатор weak в оголошеннях властивостей:
// 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 скрізь призводить до надмірної складності та погіршує читабельність. Розглянемо правильні сценарії застосування.
Делегати — основний сценарій для weak. Об'єкт-власник (наприклад, UITableView) тримає сильне посилання на себе, а делегат (UIViewController) не повинен володіти таблицею. Apple SDK гарантує, що всі delegate та dataSource — weak. Для власних протоколів завжди використовуйте weak var delegate.
Коли дочірній об'єкт повинен посилатися на батька (наприклад, ChildViewController для доступу до координатора), використовуйте weak-посилання. Батько володіє дитиною (strong), дитина спостерігає за батьком (weak) — цикл утримання виключено.
Capture list [weak self] — стандартний спосіб уникнути циклів утримання в замиканнях, що зберігаються як властивості класу. Якщо self може бути звільнено до завершення замикання — weak self обов'язковий.
| Сценарій | Weak | Strong |
|---|---|---|
| Delegate | ✅ Завжди weak | ❌ Цикл утримання |
| Батько → Дитина | ❌ Не потрібно (батько повинен володіти) | ✅ Strong |
| Дитина → Батько | ✅ Weak | ❌ Цикл утримання |
| Асинхронний callback | ✅ [weak self] | ❌ Ризик циклу утримання |
| Сильний зв'язок (owned) | ❌ unowned | ✅ Strong |
Загальне правило: якщо об'єкт A володіє B (A → B strong), то B → A повинен бути weak або unowned. Напрямок сильних посилань завжди повинен бути від власника до підлеглого.
І weak, і unowned не збільшують retain count, але відрізняються поведінкою після деалокації об'єкта. Вибір між ними — питання гарантій часу життя.
Weak: автоматично обнуляється (nil), тип завжди опціональний, потребує unwrap перед використанням. Безпечний — звернення до nil не викликає crash.
Unowned: не обнуляється, тип non-optional. Якщо об'єкт звільнено, unowned-посилання стає висячим покажчиком — звернення до нього викликає 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-розробці.
Слабкі посилання повільніші за сильні: при кожному доступі середовище виконання перевіряє, чи не звільнено об'єкт (lookup у слабкій таблиці). У переважній більшості сценаріїв різниця непомітна, але в гарячих циклах з мільйонами звернень weak може бути вузьким місцем. Для високонавантажених сценаріїв використовуйте strong та реорганізуйте архітектуру.
Struct, enum, tuple — типи значень, що не беруть участі в ARC. Спроба оголосити weak struct призводить до помилки компіляції. Для зберігання слабкого посилання на тип значення використовуйте обгортку в типі class або замикання.
Zeroing weak потокобезпечний: якщо об'єкт звільняється в одному потоці, слабке посилання обнуляється в усіх потоках атомарно. Однак проміжок між читанням слабкого посилання та розіменуванням може призвести до стану гонки — об'єкт звільняється між отриманням слабкого посилання та його використанням. Рішення: сильне захоплення слабкого посилання в локальну змінну.
// 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.
IBOutlet в Interface Builder повинні бути weak, оскільки ієрархія view вже тримає сильне посилання на subview. Дублювання сильного посилання в контролері не створює циклу утримання, але є надлишковим. Слабке посилання на outlet — рекомендація Apple, хоча багато розробників використовують strong для спрощення коду.
Поширені запитання
Ні, weak може вказувати лише на існуючий об'єкт або nil. При створенні нового об'єкта ви спочатку отримуєте сильне посилання (через ініціалізатор), і лише потім можете присвоїти слабке посилання. weak nil на початку — нормальний стан.
Weak заснований на ARC, який керує лише референсними типами (класи). Типи значень (struct, enum) копіюються при присвоєнні та не мають retain count. Для слабкого зв'язку типів значень використовуйте замикання або обгортки в class з weak-властивістю.
Кожен доступ до weak-посилання виконує lookup у таблиці середовища виконання. У циклі з мільйонами ітерацій це може бути в 2–5 разів повільніше за сильне посилання. Для гарячих шляхів скопіюйте weak у локальну сильну змінну перед циклом.
Коли всі сильні посилання на об'єкт втрачено — наприкінці області видимості, при перевстановленні властивості, при закритті екрана. У багатопотоковому середовищі це може статися між двома рядками коду. Завжди перевіряйте weak через guard let або if let.
Семантично ідентично: обидва надають zeroing weak. Відмінності: Swift вимагає опціональний тип та var, Objective-C використовує модифікатор властивості. Objective-C також підтримує __unsafe_unretained — слабке посилання без zeroing (ризик висячого покажчика).
Підсумки
weak var + опціональний тип; лише типи class та протоколи AnyObjectМи розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також