Automatic Reference Counting (ARC) — система управления памятью в Swift и Objective-C, которая автоматически подсчитывает количество ссылок на каждый объект и освобождает его, когда счётчик достигает нуля. По данным Apple Swift Documentation, 2026, ARC встраивается в компилятор и работает на этапе компиляции, вставляя вызовы retain/release в нужные места. В отличие от Garbage Collection, 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 ошибок.
Каждый объект в памяти имеет счётчик ссылок (retain count). При создании объекта счётчик устанавливается в 1. Когда новая strong-ссылка указывает на объект — счётчик увеличивается (retain). Когда strong-ссылка исчезает — счётчик уменьшается (release). При достижении нуля объект немедленно деаллоцируется.
Компилятор Swift вставляет retain/release не на каждое присваивание — он использует статический анализ для оптимизации. Например, если объект гарантированно не используется после передачи, компилятор может пропустить лишний release/retain. Эта оптимизация называется ARC Optimisation.
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 и 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 освобождает от этих забот, но ценой недетерминированных пауз и дополнительного потока.
ARC определяет три типа квалификаторов ссылок, каждый из которых по-разному влияет на счётчик и жизненный цикл объекта. Правильный выбор квалификатора — основа безопасной работы с памятью в Swift.
Strong — квалификатор по умолчанию. Каждая strong-ссылка увеличивает retain count объекта на 1. Пока существует хотя бы одна strong-ссылка, объект жив. Все свойства классов и локальные переменные в Swift по умолчанию strong. Strong-ссылки создают отношение владения: объект A владеет объектом B.
Weak — ссылка, которая не увеличивает retain count. Объект может быть деаллоцирован, даже если на него указывает weak-ссылка. После деаллокации weak-ссылка автоматически устанавливается в nil. Weak-ссылки всегда объявляются как var с опциональным типом (?). Используются для разрыва retain cycle, особенно в delegate-паттерне.
Unowned — невладеющая ссылка, которая, как и weak, не увеличивает retain count. Однако unowned-ссылка не устанавливается в nil после деаллокации — обращение к освобождённому объекту вызывает crash. Unowned применяется, когда гарантировано, что объект живёт как минимум столько же, сколько и ссылающийся объект. Типичный сценарий — замыкания и родитель-дочерние отношения с гарантированным временем жизни.
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 не панацея. Разработчики сталкиваются с несколькими типовыми проблемами, которые требуют понимания внутреннего механизма управления памятью.
Замыкания (closures) в Swift захватывают внешние переменные по strong-ссылке. Если замыкание присвоено свойству класса и захватывает self — возникает retain cycle: класс держит замыкание, замыкание держит self. Решение — capture list с weak или unowned.
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 — лёгкие операции, в горячих циклах частые инкременты/декременты счётчика дают накладные расходы. В Swift 5.9+ компилятор использует оптимизацию, при которой избыточные retain/release удаляются, если анализатор доказывает безопасность. Однако в Objective-C retain/release всё ещё могут быть узким местом в высоконагруженных сценариях с миллионами вызовов в секунду.
Autorelease Pool — механизм отложенного release, используемый в Objective-C и некоторых сценариях Swift. Объекты помещаются в пул и получают release при drain пула. В циклах с большим числом временных объектов (например, парсинг JSON) создание собственного autoreleasepool снижает пиковое потребление оперативной памяти.
Часто задаваемые вопросы
При ручном управлении (MRR) разработчик явно вызывал retain, release и autorelease. ARC вставляет эти вызовы автоматически на этапе компиляции, устраняя риск double-free, утечек из-за забытого release и ошибок балансировки retain/release.
ARC управляет только объектами Objective-C и Swift class. Для C/C++ структур и указателей ARC не применяется — эти объекты управляются вручную или через умные указатели C++ (shared_ptr, unique_ptr). Core Foundation объекты (CFString, CGColor) также не подпадают под ARC.
weak — когда объект может быть деаллоцирован раньше ссылающегося (делегаты, асинхронные замыкания). unowned — когда объект гарантированно живёт не меньше ссылающегося (родитель-ребёнок, где ребёнок не может существовать без родителя). Если не уверены — выбирайте weak.
Экзистенциальные типы (protocol as type) в Swift упаковывают значение в специальный контейнер (existential container). Это увеличивает число retain/release на границах протоколов. В Swift 5.7+ opaque result types и some-параметры снижают накладные расходы за счёт устранения контейнера.
Прямого API для чтения retain count в Swift нет — это считается деталью реализации. Для диагностики используйте Instruments (Allocations, Leaks) или Memory Debugger в Xcode. Эти инструменты показывают количество живых экземпляров класса и цепочки удержания.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также