Weak Reference (слабая ссылка) — это ссылка на объект, которая не увеличивает его счётчик удержания в ARC. По данным Apple Swift Language Guide, 2026, weak-ссылки объявляются с ключевым словом 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-типам, не к closure
}
Важно: weak применим только к экземплярам классов (class-типам), AnyObject и протоколам, унаследованным от AnyObject. Struct, enum и closure не могут быть weak — они являются value-типами и не участвуют в 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 везде подряд приводит к избыточной сложности и ухудшает читаемость. Рассмотрим правильные сценарии применения.
Делегат — основной сценарий для 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) может быть закрыт и освобождён в любой момент.
Weak-ссылки — мощный инструмент, но они имеют ограничения, которые важно понимать для корректного применения в iOS разработке.
Weak-ссылки медленнее strong: при каждом доступе runtime проверяет, не освобождён ли объект (lookup в weak-таблице). В подавляющем большинстве сценариев разница неощутима, но в горячих циклах с миллионами обращений weak может быть узким местом. Для высоконагруженных сценариев используйте strong и реорганизуйте архитектуру.
Struct, enum, tuple — value-типы, не участвующие в ARC. Попытка объявить weak struct приводит к ошибке компиляции. Для хранения слабой ссылки на value-тип используйте обёртку в class-тип или замыкание.
Zeroing weak потокобезопасен: если объект освобождается на одном потоке, weak-ссылка обнуляется на всех потоках атомарно. Однако промежуток между чтением weak-ссылки и разыменованием может привести к состоянию гонки — объект освобождается между получением weak-ссылки и её использованием. Решение: strong-захват слабой ссылки в локальную переменную.
// 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 — локальная strong-ссылка
}
}
В безопасном варианте weak self захватывается, затем сразу разворачивается в локальную strong-переменную strongSelf. Если self ещё жив — он останется живым на время выполнения блока. Если нет — guard срабатывает, и код не выполняется. Этот idiom — стандартный паттерн для асинхронных замыканий в Swift.
IBOutlet в Interface Builder должны быть weak, так как view hierarchy уже держит strong-ссылку на subview. Дублирование strong-ссылки в контроллере не создаёт retain cycle, но является избыточным. Слабая ссылка на outlet — рекомендация Apple, хотя многие разработчики используют strong для упрощения кода.
Часто задаваемые вопросы
Нет, weak может указывать только на существующий объект или nil. При создании нового объекта вы сначала получаете strong-ссылку (через инициализатор), и только потом можете присвоить слабую ссылку. weak nil в начале — нормальное состояние.
Weak основан на ARC, который управляет только reference-типами (классы). Value-типы (struct, enum) копируются при присваивании и не имеют retain count. Для слабой связи value-типов используйте замыкания или обёртки в 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 + optional-тип; только class-типы и AnyObject-протоколыМы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также