Weak Reference — что это, синтаксис и применение в мобильной разработке

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

Weak Reference (слабая ссылка) — это ссылка на объект, которая не увеличивает его счётчик удержания в ARC. По данным Apple Swift Language Guide, 2026, weak-ссылки объявляются с ключевым словом 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-типам, не к closure
}

Важно: weak применим только к экземплярам классов (class-типам), AnyObject и протоколам, унаследованным от AnyObject. Struct, enum и closure не могут быть weak — они являются value-типами и не участвуют в 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-ссылки — не универсальное решение, а инструмент для конкретных сценариев. Использование 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 ссылок и подводные камни

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

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

Weak-ссылки медленнее strong: при каждом доступе runtime проверяет, не освобождён ли объект (lookup в weak-таблице). В подавляющем большинстве сценариев разница неощутима, но в горячих циклах с миллионами обращений weak может быть узким местом. Для высоконагруженных сценариев используйте strong и реорганизуйте архитектуру.

Weak не применим к value-типам

Struct, enum, tuple — value-типы, не участвующие в ARC. Попытка объявить weak struct приводит к ошибке компиляции. Для хранения слабой ссылки на value-тип используйте обёртку в class-тип или замыкание.

Weak в многопоточности

Zeroing weak потокобезопасен: если объект освобождается на одном потоке, weak-ссылка обнуляется на всех потоках атомарно. Однако промежуток между чтением weak-ссылки и разыменованием может привести к состоянию гонки — объект освобождается между получением weak-ссылки и её использованием. Решение: strong-захват слабой ссылки в локальную переменную.

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 — локальная strong-ссылка
    }
}

В безопасном варианте weak self захватывается, затем сразу разворачивается в локальную strong-переменную strongSelf. Если self ещё жив — он останется живым на время выполнения блока. Если нет — guard срабатывает, и код не выполняется. Этот idiom — стандартный паттерн для асинхронных замыканий в Swift.

UIView и weak outlet

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

Часто задаваемые вопросы

Может ли weak-ссылка указывать на объект, который ещё не создан?

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

Почему weak работает только с class-типами?

Weak основан на ARC, который управляет только reference-типами (классы). Value-типы (struct, enum) копируются при присваивании и не имеют retain count. Для слабой связи value-типов используйте замыкания или обёртки в 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 + optional-тип; только class-типы и 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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