> Как работает механизм управления памятью ARC в Swift (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Битрикс24

Стек: iOS, Swift

> Пример ответа

Короткий ответ

ARC (Automatic Reference Counting) - это механизм автоматического управления памятью в Swift, основанный на подсчёте ссылок на объекты классов. Каждый экземпляр класса имеет счётчик сильных ссылок, который увеличивается при создании новой сильной ссылки и уменьшается при её уничтожении. Когда счётчик достигает нуля, память объекта немедленно освобождается. ARC работает только на этапе компиляции, вставляя вызовы retain/release, и не использует garbage collection или runtime-мониторинг.

Подробное объяснение

ARC управляет памятью только для reference types (классы, замыкания), а value types (структуры, перечисления) хранятся в стеке или внутри других объектов и не требуют подсчёта ссылок. Основные элементы ARC:

  • Сильные ссылки (strong) - по умолчанию, увеличивают счётчик на 1.
  • Слабые ссылки (weak) - не увеличивают счётчик, автоматически становятся nil при освобождении объекта. Используются для делегатов и observer-паттернов.
  • Бесхозные ссылки (unowned) - не увеличивают счётчик, но не обнуляются. Предполагают, что объект живёт столько же, сколько и владелец. Обращение к освобождённому объекту через unowned вызывает crash.

Ключевая проблема ARC - retain cycles: когда два объекта держат сильные ссылки друг на друга, их счётчики никогда не достигнут нуля. Для решения используются weak/unowned ссылки или capture lists в замыканиях.

ARC также влияет на производительность: каждый retain/release - это атомарная операция (если не указан nonisolated(unsafe) или unowned), что может быть дорого в горячих путях. Оптимизации компилятора (например, -O с оптимизацией ARC) могут удалять избыточные вызовы.

Важно понимать, что ARC не управляет памятью для C-указателей, Core Foundation объектов или raw memory - для них нужны ручные вызовы или Unmanaged.

На практике

В реальной разработке ARC требует внимания в следующих сценариях:

  • Замыкания и capture lists: если замыкание захватывает self сильно, а self хранит замыкание - цикл. Используйте [weak self] или [unowned self] (только если self гарантированно живёт дольше замыкания).
  • Делегаты и dataSource: объявляйте их как weak var, чтобы избежать циклов между view controller и его делегатом.
  • Многопоточность: ARC не потокобезопасен по умолчанию для одного объекта - нужны дополнительные синхронизации, если объект мутируется из разных потоков.
  • Производительность: в цикле с большим количеством объектов избегайте лишних сильных захватов, используйте withExtendedLifetime для гарантии жизни объекта на время операции.
  • Диагностика: инструменты Instruments (Leaks, Allocations) помогают находить утечки, но retain cycle часто виден по тому, что deinit не вызывается.

Пример кода

SWIFT
class Parent {
var child: Child?
deinit { print("Parent deinit") }
}
class Child {
weak var parent: Parent? // weak - нет цикла
deinit { print("Child deinit") }
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent
parent = nil
child = nil
// Оба deinit вызываются корректно
// Retain cycle через замыкание
class ViewModel {
var onUpdate: (() -> Void)?
func setup() {
onUpdate = { [weak self] in
self?.doSomething()
}
}
func doSomething() {}
}

Как отвечать на собеседовании

Начните с краткого определения ARC, затем переходите к деталям: различие strong/weak/unowned, механизм вставки retain/release на этапе компиляции, отличие от GC (нет фоновых потоков, детерминированное освобождение). Обязательно упомяните retain cycles и способы их избегания. Если спросят про производительность - скажите, что ARC добавляет оверхед на каждый retain/release, но компилятор оптимизирует, и в большинстве случаев это не критично. Для senior-уровня добавьте про @unchecked Sendable и проблемы ARC в многопоточности, а также про взаимодействие с C-кодом через Unmanaged.

Что проверяет интервьюер

  • Понимание разницы между reference и value types в контексте памяти.
  • Знание семантики weak/unowned и когда их применять.
  • Умение находить retain cycles в коде и объяснять, почему они возникают.
  • Понимание, что ARC - compile-time механизм, а не runtime.
  • Осознание ограничений ARC (не работает для C-указателей, не потокобезопасен).
  • Способность объяснить trade-off между ARC и ручным управлением памятью.

Типичные ошибки

  • Утверждение, что ARC - это garbage collector.
  • Использование unowned там, где объект может стать nil (например, в замыкании, которое переживает владельца) - это приводит к crash.
  • Забывание про retain cycle в замыканиях, особенно при работе с DispatchQueue или Timer.
  • Путаница между weak и unowned - первая безопаснее, вторая быстрее, но требует гарантий.
  • Непонимание, что ARC не управляет памятью структур, но структуры могут содержать ссылочные типы.
  • Игнорирование влияния ARC на производительность в циклах с большим количеством объектов.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью