ARC: що це, принцип роботи Automatic Reference Counting у iOS

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

Automatic Reference Counting (ARC) — система керування пам'яттю у Swift та Objective-C, яка автоматично підраховує кількість посилань на кожен об'єкт і звільняє його, коли лічильник досягає нуля. Згідно з Документацією Apple Swift, 2026, ARC вбудовано в компілятор і він працює на етапі компіляції, вставляючи виклики retain/release у потрібні місця. На відміну від Garbage Collection, ARC не потребує окремого потоку збирача та не створює пауз під час виконання застосунку.

Головне

  • ARC — Automatic Reference Counting, компіляторна система керування пам'яттю у Swift та Objective-C
  • Принцип роботи — кожен об'єкт має лічильник посилань (retain count), при обнулінні об'єкт негайно звільняється
  • Кваліфікатори — strong, weak та unowned визначають, як посилання впливає на лічильник і життєвий цикл об'єкта
  • Відмінність від GC — ARC працює детерміновано на етапі компіляції, без Stop-The-World пауз і фонового потоку збирача
  • Retain Cycle — головна проблема ARC: якщо два об'єкти посилаються один на одного через strong, їхній лічильник ніколи не обнуляється

Що таке ARC?

ARC (Automatic Reference Counting) — це компіляторний механізм керування пам'яттю, представлений Apple у Xcode 4.2 (2011) для Objective-C та успадкований Swift. На відміну від ручного керування пам'яттю (Manual Retain-Release, MRR), ARC повністю автоматизує виклики retain, release та autorelease, вставляючи їх на етапі компіляції без участі розробника.

ARC не є збирачем сміття. Це статичний аналіз із динамічним вставленням коду: компілятор аналізує час життя об'єктів і розміщує retain/release у точках, де об'єкти створюються, копіюються або виходять з області видимості. Результат — детерміноване звільнення пам'яті: об'єкт видаляється саме тоді, коли на нього більше немає посилань, без затримок і пауз.

Згідно з WWDC 2011 Session 323, перехід з MRR на ARC скоротив кількість помилок, пов'язаних із пам'яттю, на 70% у застосунках Apple. Розробники перестали вручну балансувати retain/release, що усунуло цілий клас витоків і double-free помилок.

Як працює Automatic Reference Counting

Кожен об'єкт у пам'яті має лічильник посилань (retain count). При створенні об'єкта лічильник встановлюється на 1. Коли нове strong-посилання вказує на об'єкт — лічильник збільшується (retain). Коли strong-посилання зникає — лічильник зменшується (release). При досягненні нуля об'єкт негайно деалокується.

Компілятор Swift вставляє retain/release не на кожне присвоєння — він використовує статичний аналіз для оптимізації. Наприклад, якщо гарантовано, що об'єкт не використовується після передачі, компілятор може пропустити зайвий release/retain. Ця оптимізація називається ARC Optimization.

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) initialized (retain count: 1)")
    }
    deinit {
        print("\(name) deallocated")
    }
}

func testARC() {
    let p = Person(name: "Alice")  // retain count = 1
    let q = p                      // retain count = 2
    // q виходить з області видимості
    // retain count = 1
    // p виходить з області видимості
    // retain count = 0 → deinit
}

У цьому прикладі видно, як ARC керує лічильником: при присвоєнні q = p лічильник збільшується, при виході q з області видимості — зменшується. Коли останнє strong-посилання зникає, деініціалізатор викликається негайно. Жоден збирач сміття не чекає — пам'ять звільняється одразу.

ARC vs Garbage Collection: ключові відмінності

ARC та Garbage Collection вирішують одне завдання — автоматичне керування пам'яттю — але принципово різними підходами. Вибір між ними визначає архітектуру мови: Swift (ARC) vs Java/Go (GC). Розглянемо основні відмінності.

ХарактеристикаARC (Swift/ObjC)GC (Java/Go)
Момент звільненняДетермінований: одразу при обнулінні лічильникаНедетермінований: при наступному збиранні
Паузи виконанняНемає (retain/release на етапі компіляції)Stop-The-World паузи (2–200 мс)
Накладні витратиІнкремент/декремент лічильника при кожному посиланніОбхід графа об'єктів, маркування, звільнення
ПроблемиRetain Cycle (ручне вирішення)Фрагментація купи, витоки через забуті посилання
Додатковий потікНе потрібенПотрібен потік збирача сміття

Ключовий компроміс: ARC дає передбачуваний час життя об'єктів і нульові паузи, але вимагає від розробника розуміння retain cycle та правильного вибору weak/unowned. GC звільняє від цих турбот, але ціною недетермінованих пауз і додаткового потоку.

Strong, Weak та Unowned: кваліфікатори посилань в ARC

ARC визначає три типи кваліфікаторів посилань, кожен з яких по-різному впливає на лічильник і життєвий цикл об'єкта. Правильний вибір кваліфікатора — основа безпечної роботи з пам'яттю у Swift.

Strong

Strong — кваліфікатор за замовчуванням. Кожне strong-посилання збільшує retain count об'єкта на 1. Поки існує хоча б одне strong-посилання, об'єкт живий. Усі властивості класів і локальні змінні у Swift за замовчуванням strong. Strong-посилання створюють відношення володіння: об'єкт A володіє об'єктом B.

Weak

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

Unowned

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

swift
class Customer {
    let name: String
    var card: CreditCard?         // strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deallocated") }
}

class CreditCard {
    let number: String
    unowned let customer: Customer   // unowned — не володіє
    init(number: String, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) deallocated") }
}

var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer та CreditCard обидва звільнені — немає retain cycle

Тут CreditCard використовує unowned-посилання на Customer. Customer володіє карткою (strong), а картка не володіє клієнтом (unowned). Коли Customer обнуляється, обидва об'єкти звільняються — retain cycle не виникає. Якби card.customer був strong, цикл заблокував би звільнення.

Типові проблеми ARC та їх вирішення

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

Retain Cycle у замиканнях

Замикання (closures) у Swift захоплюють зовнішні змінні за strong-посиланням. Якщо замикання присвоєно властивості класу і захоплює self — виникає retain cycle: клас тримає замикання, замикання тримає self. Рішення — capture list з weak або unowned.

swift
class NetworkManager {
    var completionHandler: ((Data?) -> Void)?
    var data: Data?

    func fetchData() {
        completionHandler = { [weak self] result in
            guard let self else { return }
            self.data = result
            self.processResult()
        }
    }

    func processResult() { }
}

Capture list [weak self] створює weak-посилання на self всередині замикання. Це розриває потенційний retain cycle. Guard let self гарантує, що об'єкт живий перед виконанням коду. weak self — стандартна практика для асинхронних замикань у Swift.

Продуктивність retain/release

Хоча retain/release — легкі операції, у гарячих циклах часті інкременти/декременти лічильника дають накладні витрати. У Swift 5.9+ компілятор використовує оптимізацію, при якій надлишкові retain/release видаляються, якщо аналізатор доводить безпеку. Однак в Objective-C retain/release все ще можуть бути вузьким місцем у високонавантажених сценаріях із мільйонами викликів на секунду.

Autorelease Pool

Autorelease Pool — механізм відкладеного release, який використовується в Objective-C та деяких сценаріях Swift. Об'єкти поміщаються в пул і отримують release при drain пула. У циклах з великою кількістю тимчасових об'єктів (наприклад, парсинг JSON) створення власного autoreleasepool знижує пікове споживання оперативної пам'яті.

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

Чим ARC відрізняється від ручного керування пам'яттю (MRR)?

При ручному керуванні (MRR) розробник явно викликав retain, release та autorelease. ARC вставляє ці виклики автоматично на етапі компіляції, усуваючи ризик double-free, витоків через забутий release та помилок балансування retain/release.

Чи може ARC працювати з кодом C/C++?

ARC керує лише об'єктами Objective-C та класами Swift. Для структур і вказівників C/C++ ARC не застосовується — ці об'єкти керуються вручну або через розумні вказівники C++ (shared_ptr, unique_ptr). Об'єкти Core Foundation (CFString, CGColor) також не підпадають під ARC.

Коли використовувати weak, а коли unowned?

weak — коли об'єкт може бути деаллокований раніше за той, що посилається (делегати, асинхронні замикання). unowned — коли гарантовано, що об'єкт живе щонайменше стільки ж, скільки й той, що посилається (батько-дитина, де дитина не може існувати без батька). Якщо не впевнені — обирайте weak.

Що таке екзистенційні типи і як вони впливають на ARC?

Екзистенційні типи (protocol as type) у Swift упаковують значення у спеціальний контейнер (existential container). Це збільшує кількість retain/release на межах протоколів. У Swift 5.7+ opaque result types та some-параметри знижують накладні витрати за рахунок усунення контейнера.

Як перевірити retain count у Swift?

Прямого API для читання retain count у Swift немає — це вважається деталлю реалізації. Для діагностики використовуйте Instruments (Allocations, Leaks) або Memory Debugger у Xcode. Ці інструменти показують кількість живих екземплярів класу та ланцюжки утримання.

Підсумки

  • ARC — компіляторна система керування пам'яттю Swift та Objective-C, що працює через підрахунок посилань
  • Принцип — кожен об'єкт має retain count; при обнулінні об'єкт звільняється негайно та детерміновано
  • Відмінність від GC — ARC працює без фонового потоку та Stop-The-World пауз, але потребує контролю retain cycle
  • Strong — збільшує лічильник; weak та unowned — не збільшують, але unowned не обнуляється при деалокації
  • Замикання — основна причина retain cycle у Swift; capture list [weak self] — стандартне рішення
  • Autorelease Pool — механізм відкладеного звільнення для тимчасових об'єктів у циклах та кастомних сценаріях
  • Діагностика — Xcode Memory Debugger, Instruments та LeakCanary (через ObjC bridge) для пошуку проблем

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

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

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

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