Как работает Automatic Reference Counting (ARC) во время выполнения программы?

«Как работает Automatic Reference Counting (ARC) во время выполнения программы?» — вопрос из категории Управление памятью, который задают на 23% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

ARC (Automatic Reference Counting) — это механизм управления памятью в Swift и Objective-C, который автоматически подсчитывает сильные ссылки на экземпляры классов во время выполнения (runtime) и освобождает память, когда счетчик достигает нуля.

Принцип работы во время выполнения:

  1. Создание объекта: При вызове init() ARC выделяет память и инициализирует счетчик ссылок (Retain Count, RC) равным 1.
  2. Увеличение счетчика (Retain): При каждом присваивании объекта новой сильной (strong) переменной или константе, runtime увеличивает RC.
    class MyClass {}
    var obj1: MyClass? = MyClass() // RC = 1 после init
    var obj2 = obj1 // RC = 2 (новая сильная ссылка)
  3. Уменьшение счетчика (Release): Когда сильная ссылка выходит из области видимости (например, функция завершилась) или ей присваивается nil, runtime уменьшает RC.
    func scope() {
        let temp = MyClass() // RC = 1 внутри функции
    } // При выходе из функции `temp` уничтожается, RC = 0 -> память освобождается
  4. Освобождение памяти (Deallocation): В момент, когда RC становится равным 0, runtime немедленно вызывает деинициализатор deinit и освобождает занятую объектом память.

Что делает runtime для реализации ARC:

  • Вставляет вызовы retain и release в скомпилированный код в соответствующих местах.
  • Ведет скрытую таблицу ссылок для каждого объекта.
  • При оптимизациях (например, -O) может пропускать избыточные вызовы retain/release.

Важное следствие: ARC не управляет циклическими ссылками (когда два объекта сильно ссылаются друг на друга). Для их разрыва необходимо использовать weak (счетчик не увеличивается) или unowned ссылки.

Пример полного цикла:

class Person {
    var apartment: Apartment?
    deinit { print("Person freed") }
}
class Apartment {
    weak var tenant: Person? // `weak` разрывает цикл
    deinit { print("Apartment freed") }
}

var john: Person? = Person() // Person RC = 1
var unit4A: Apartment? = Apartment() // Apartment RC = 1

john?.apartment = unit4A // Apartment RC = 2 (сильная ссылка из john)
unit4A?.tenant = john // Person RC НЕ увеличивается, так как ссылка weak

john = nil // Person RC = 0 -> "Person freed"
// Теперь ссылка из apartment на john = nil
unit4A = nil // Apartment RC = 0 -> "Apartment freed"