Strong Reference (сильне посилання): що це, механізм роботи та ARC

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

Strong Reference (сильне посилання) — це стандартний механізм керування пам’яттю, при якому об’єкт залишається в пам’яті, поки на нього вказує хоча б одне активне посилання. На відміну від слабких посилань, сильне посилання збільшує лічильник посилань об’єкта та запобігає його автоматичному звільненню. Згідно з Apple Developer Documentation, ARC автоматично керує часом життя об’єктів в Swift та Objective-C. Розуміння роботи сильних посилань критично для запобігання витоків пам’яті та циклічних залежностей в мобільних додатках.

Головне

  • Strong Reference — посилання, яке утримує об’єкт в пам’яті, збільшуючи його retain count на 1.
  • ARC автоматично вставляє операції retain та release, позбавляючи від ручного керування пам’яттю в Swift та Objective-C.
  • Retain cycle виникає, коли два об’єкти посилаються один на одного через сильні посилання — пам’ять ніколи не звільняється.
  • Weak Reference не збільшує лічильник посилань і автоматично обнуляється при звільненні об’єкта.
  • Unowned Reference не збільшує лічильник, але передбачає, що об’єкт живе не довше за власника.

Що таке Strong Reference?

Strong Reference — це тип посилання на об’єкт, який запобігає його знищенню збірувачем сміття або системою керування пам’яттю. Поки існує хоча б одне сильне посилання на об’єкт, його пам’ять не звільняється. Це базовий механізм, на якому побудовані ARC в Swift та Objective-C і збірання сміття в Java та Kotlin.

Концепція сильного посилання є фундаментальною для всіх мов з автоматичним керуванням пам’яттю. В системах з ARC кожне сильне посилання збільшує лічильник посилань об’єкта. Коли лічильник падає до нуля, об’єкт негайно деалокується. В Java та Kotlin з збіранням сміття сильне посилання гарантує, що об’єкт досяжний і не буде зібраний GC.

За даними WWDC 2021, близько 35% витоків пам’яті в iOS-додатках пов’язані з неправильним використанням сильних посилань та циклами утримання. В Android-розробці витоки через неявні strong reference в замиканнях та зворотних викликах є другою за частотою причиною проблем з пам’яттю після Context Leak.

Для ефективної роботи з пам’яттю необхідно розуміти різницю між strong, weak та unowned посиланнями і правильно обирати тип посилання залежно від володіння та часу життя об’єктів.

Як ARC змінив підхід до керування пам’яттю

До впровадження ARC розробники вручну викликали retain та release для кожного об’єкта, що призводило до багатьох помилок. ARC, представлений Apple у 2011 році з випуском LLVM 3.0, автоматизував цей процес, аналізуючи граф володіння на етапі компіляції. Компілятор сам вставляє виклики retain, release та autorelease в потрібні місця.

За даними Clang Static Analyzer, впровадження ARC знизило кількість помилок, пов’язаних з пам’яттю, в iOS-додатках на 70%. Для розробника це означає, що керування пам’яттю стало безпечнішим, але водночас з’явилася необхідність розуміти, як працюють сильні посилання під капотом — щоб уникати retain cycles.

В Kotlin та Java роль ARC відіграє збірувач сміття, але принцип сильного посилання залишається тим самим: GC Roots — це точки входу, через які об’єкти утримуються сильними посиланнями. Поки об’єкт досяжний по ланцюжку сильних посилань від GC Root, він не буде зібраний.

Як працює Strong Reference в ARC?

ARC (Automatic Reference Counting) працює за принципом підрахунку посилань для кожного об’єкта в купі. Коли створюється нове сильне посилання на об’єкт, лічильник збільшується (retain). Коли посилання знищується або перезаписується, лічильник зменшується (release). Коли лічильник досягає нуля, об’єкт негайно видаляється з пам’яті.

Розглянемо приклад на Swift. При створенні екземпляра класу ARC виділяє пам’ять та встановлює retain count на 1. Кожне нове присвоєння іншій змінній збільшує лічильник. Коли змінна виходить з області видимості, лічильник зменшується:

swift
class ProfileViewController {
    var nameLabel: String?
    var avatarImage: UIImage?

    func loadProfile() {
        // retain count = 1 для нового екземпляра
        let user = User(name: "Ivan")
        // retain count = 2 після присвоєння nameLabel
        nameLabel = user.name
        // вихід з методу — user виходить з області видимості, retain count = 1
    }
}

В цьому коді ARC гарантує, що об’єкт User залишається в пам’яті, поки на нього є хоча б одне сильне посилання. Коли функція loadProfile завершується, локальна змінна user знищується, але nameLabel все ще утримує об’єкт. Пам’ять звільниться тільки тоді, коли nameLabel перестане існувати або буде перезаписана.

В Kotlin аналогічна поведінка забезпечується через GC Roots. Поки існує простежуваний ланцюжок сильних посилань від кореня збірувача сміття (наприклад, статичне поле або активний потік), об’єкт залишається в пам’яті. Різниця в тому, що GC не звільняє пам’ять миттєво — це відбувається асинхронно після аналізу досяжності.

Коли відбувається звільнення пам’яті

В ARC звільнення відбувається синхронно в момент обнулення лічильника. В Swift та Objective-C ви точно знаєте, коли об’єкт буде видалений. В Kotlin та Java момент звільнення непередбачуваний, але це компенсується більш гнучкою схемою виявлення циклічних залежностей на рівні збірувача сміття.

Retain Cycles та витоки пам’яті

Retain cycle (цикл утримання) — ситуація, при якій два або більше об’єктів мають взаємні сильні посилання один на одного. У результаті їхній retain count ніколи не падає до нуля, і пам’ять не звільняється навіть після того, як об’єкти перестають бути потрібними додатку.

Класичний приклад: батьківський view controller утримує дочірній об’єкт сильним посиланням, а той, у свою чергу, сильним посиланням утримує батька. Це типово для ситуацій з делегатами, замиканнями та вкладеними lambda-виразами. За даними Instruments Leaks, retain cycles становлять до 60% усіх витоків пам’яті в додатках, що використовують ARC.

swift
class ParentViewController: UIViewController {
    var child: ChildViewController?

    func setupChild() {
        child = ChildViewController()
        // retain cycle: батько утримує child, child утримує батька через closure
        child?.onEvent = {
            self.handleEvent()
        }
    }

    func handleEvent() {}
}

Проблема тут в тому, що замикання onEvent захоплює self (ParentViewController) сильним посиланням, а сам ParentViewController утримує child сильним посиланням. Обидва об’єкти ніколи не будуть звільнені. Рішення — використовувати weak self в замиканні, щоб розірвати цикл.

В Kotlin аналогічні цикли виникають при використанні lambda, що захоплюють зовнішні об’єкти. JVM збірувач сміття може з часом виявити такі цикли, але тільки якщо об’єкти недосяжні з GC Roots. Якщо цикл пов’язаний з активним потоком або UI-контекстом, витік залишається на весь час життя додатка.

Strong vs Weak vs Unowned Reference

Розуміння різниці між типами посилань — ключ до безпечного керування пам’яттю. Strong Reference збільшує retain count. Weak Reference не збільшує retain count і автоматично стає nil при звільненні об’єкта. Unowned Reference також не збільшує retain count, але не обнуляється — звернення до нього після звільнення викликає краш.

Тип посиланняRetain countБезпекаКоли використовувати
Strong+1Безпечно (типово)Володіння об’єктом, відносини батько → дитина
WeakНе змінюєАвтообнулення (безпечно)Делегати, callback, зворотні посилання
UnownedНе змінюєРизик крашу при пізньому зверненніКоли об’єкт живе гарантовано довше за власника

Вибір типу посилання диктується відносинами володіння. Якщо об’єкт B є частиною A і не може існувати без нього — використовуйте Strong. Якщо B може існувати незалежно і посилається на A для сповіщень — використовуйте Weak. Unowned використовується рідко — тільки коли час життя дочірнього об’єкта суворо не перевищує час життя батька.

Практичне правило вибору

Apple Developer Documentation рекомендує: типово використовуйте strong для всіх відносин володіння. Якщо необхідно уникнути retain cycle — визначте, яке посилання має бути слабким. Зазвичай це зворотне посилання в ієрархії (дитина → батько). В Kotlin аналогічну роль відіграє WeakReference з java.lang.ref, який використовується для кешів та патернів observer.

Як виправити проблеми з сильними посиланнями

Виявлення retain cycles — перший крок. Другий — правильне їх усунення. Основний інструмент боротьби з циклами сильних посилань — заміна одного з посилань на weak або unowned. В мовах зі збіранням сміття додатково застосовується WeakReference з ручною перевіркою на null перед кожним доступом.

В Swift та Objective-C найчастішим виправленням є додавання [weak self] в замикання. Це гарантує, що замикання не утримує об’єкт після його звільнення. В Kotlin для аналогічних цілей використовують обгортки WeakReference або явне очищення посилань в onDestroy.

swift
class NetworkService {
    func fetchData(completion: @escaping (Data?) -> Void) {
        // захоплення через weak self — retain cycle виключено
        URLSession.shared.dataTask(
            with: URL(string: "https://api.example.com")!
        ) { [weak self] data, response, error in
            guard let self else { return }
            completion(data)
        }.resume()
    }
}

В цьому прикладі [weak self] гарантує, що NetworkService не буде утримуватися замиканням після того, як він більше не потрібний. Якщо self звільнився до завершення запиту — guard let self else { return } виходить з замикання без виклику completion.

Для діагностики retain cycles використовуйте Instruments Leaks для iOS або Android Profiler + LeakCanary для Android. Ці інструменти показують точний граф утримання та вказують, яке сильне посилання перешкоджає звільненню об’єкта. Регулярне профілювання пам’яті має бути частиною CI/CD пайплайну будь-якого мобільного проекту.

Strong Reference в Swift та Kotlin — порівняння

Swift та Kotlin використовують принципово різні механізми керування пам’яттю, але концепція сильного посилання присутня в обох. Swift використовує ARC з синхронним звільненням при retain count = 0. Kotlin використовує трасуючий GC, який асинхронно вичищає недосяжні об’єкти.

ПараметрSwift (ARC)Kotlin (JVM GC)
МеханізмПідрахунок посилань (retain count)Трасування досяжності (GC Roots)
ЗвільненняСинхронне (при обнуленні лічильника)Асинхронне (по циклу GC)
Retain cycleНе виявляється автоматичноGC може виявити, але не відразу
Слабке посиланняweak (автообнулення)WeakReference (ручна перевірка)

Основна практична відмінність: в Swift retain cycle — це гарантований витік. В Kotlin GC може розірвати цикл, якщо об’єкти недосяжні з кореня, але час життя витеклих об’єктів залишається непередбачуваним. Тому в обох мовах найкраща стратегія — уникати циклів сильних посилань на етапі проектування.

Для Swift використовуйте weak в делегатських патернах та замиканнях. Для Kotlin — WeakReference або Lifecycle-aware компоненти, які автоматично очищають посилання при знищенні власника. В обох підходах мета одна — виключити сильні посилання там, де вони створюють нерозривний ланцюг утримання.

Часті запитання

Чим Strong Reference відрізняється від Weak Reference?

Strong Reference збільшує retain count об’єкта та запобігає його звільненню, поки існує посилання. Weak Reference не змінює retain count та автоматично стає nil, коли об’єкт видаляється з пам’яті. Сильні посилання використовуються для володіння, слабкі — для зворотних зв’язків та делегатів.

Що таке retain cycle і чому він небезпечний?

Retain cycle — взаємне блокування, при якому два об’єкти утримують один одного сильними посиланнями. Їхній retain count ніколи не падає до нуля, пам’ять не звільняється. Це призводить до витоку пам’яті: об’єкти назавжди залишаються в купі, додаток споживає все більше ресурсів і в результаті падає з OutOfMemory.

Як виявити retain cycle в iOS додатку?

Використовуйте Instruments Leaks з Xcode — запустіть профілювання з шаблоном Leaks, виконайте сценарій в додатку та перевірте індикатори витоків. Для точної діагностики перейдіть на вкладку Cycles & Roots — вона покаже граф взаємних strong reference, які утворюють нерозривний цикл.

Коли потрібно використовувати Unowned замість Weak?

Unowned застосовуйте, коли час життя дочірнього об’єкта завідомо не перевищує час життя батька — наприклад, при прив’язці об’єкта до суворо визначеного області. Якщо є сумніви — використовуйте Weak, оскільки звернення до звільненого unowned викликає краш додатку.

Чи впливають сильні посилання на продуктивність додатка?

Опосередковано — так. Кожен retain та release в ARC — це атомна операція з накладними витратами. При великій кількості об’єктів у циклах це може впливати на продуктивність. Однак основна проблема — не швидкість ARC, а витоки пам’яті через неправильно вибраний тип посилання.

Підсумки

  • Strong Reference — базовий механізм володіння об’єктом, що утримує його в пам’яті через збільшення retain count.
  • ARC автоматизує керування пам’яттю в Swift та Objective-C, виключаючи ручні retain та release, але не захищає від retain cycles.
  • Retain cycle виникає при взаємних сильних посиланнях — це основна причина витоків пам’яті в ARC-системах.
  • Weak та Unowned посилання розривають цикли сильних посилань, не збільшуючи retain count.
  • Вибір типу посилання визначається відносинами володіння: Strong для батька→дитини, Weak або Unowned для дитини→батька.
  • Instruments Leaks та LeakCanary — основні інструменти для виявлення проблемних сильних посилань в iOS та Android.
  • Проектуйте граф володіння заздалегідь — це дешевше, ніж виправляти витоки пам’яті після релізу додатка.

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

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

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

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