ARC: что это, принцип работы Automatic Reference Counting в iOS

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

Automatic Reference Counting (ARC) — система управления памятью в Swift и Objective-C, которая автоматически подсчитывает количество ссылок на каждый объект и освобождает его, когда счётчик достигает нуля. По данным Apple Swift Documentation, 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 сократил количество crash-багов, связанных с памятью, на 70% в приложениях Apple. Разработчики перестали вручную балансировать retain/release, что устранило целый класс утечек и double-free ошибок.

Как работает Automatic Reference Counting

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

Компилятор Swift вставляет retain/release не на каждое присваивание — он использует статический анализ для оптимизации. Например, если объект гарантированно не используется после передачи, компилятор может пропустить лишний release/retain. Эта оптимизация называется ARC Optimisation.

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 class. Для 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 года. Мы проконсультируем вас и предложим наилучшее решение.

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

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