← Назад к вопросам

Как до ARC работал RC?

3.0 Senior🔥 81 комментариев
#Управление памятью

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм подсчета ссылок (RC) до введения ARC

В эпоху до автоматического подсчета ссылок (ARC, Automatic Reference Counting), которая началась в Objective-C и продолжалась до его широкого использования в iOS/Mac разработке, управление памятью осуществлялось через Manual Reference Counting (MRC) или просто Reference Counting (RC). Это был полностью ручной процесс, где разработчик самостоятельно управлял жизненным циклом объектов, увеличивая и уменьшая счетчик ссылок.

Основные принципы ручного подсчета ссылок

В системе MRC каждый объект имеет внутренний счетчик — retain count. Этот счетчик увеличивается при создании новой ссылки на объект и уменьшается при ее уничтожении. Когда retain count достигает нуля, объект немедленно освобождается из памяти (dealloc).

Ключевые методы для управления счетчиком:

  • retain: Увеличивает счетчик ссылок на 1. Вызывается, когда другая часть кода хочет "владеть" объектом.
  • release: Уменьшает счетчик ссылок на 1. Вызывается, когда владелец ссылки больше не нуждается в объекте.
  • autorelease: Добавляет объект в autorelease pool, который будет автоматически вызвать release на всех своих объектах в конце текущего цикла событий (обычно в конце runloop). Это удобно для возврата объектов из методов без передачи владения.

Пример базового использования в Objective-C:

// MRC пример
- (void)exampleMRC {
    // Создание объекта: retain count = 1
    NSMutableArray *array = [[NSMutableArray alloc] init];

    // retain count становится 2
    [array retain];

    // retain count становится 1
    [array release];

    // retain count становится 0, объект уничтожается, память освобождается
    [array release];
}

Правила и сложности ручного управления

Разработчик должен был строго соблюдать набор правил:

  1. Создание через alloc, new, copy, mutableCopy: Эти методы возвращают объект с retain count = 1. Ответственность за его release лежит на вызывающей стороне.
  2. Получение объекта из других методов: Если метод не использует вышеуказанные ключевые слова, он обычно возвращает autoreleased объект. Если вам нужно сохранить его долго, вы должны явно вызвать retain.
  3. Баланс retain/release: Каждый retain должен быть уравновешен соответствующим release. Несоблюдение приводит либо к утечке памяти (счетчик никогда не становится 0), либо к раннему освобождению и краху при обращении к удаленному объекту (dangling pointer).
// Пример сложности: возврат объекта из метода
- (NSMutableArray *)createArrayMRC {
    // retain count = 1
    NSMutableArray *arr = [[NSMutableArray alloc] init];
    // Добавляем в autorelease pool, чтобы передать без владения
    // Вызывающий код должен будет сделать retain, если хочет владеть
    return [arr autorelease];
}

- (void)useArrayMRC {
    // Получение autoreleased объекта (retain count = 1, но в пуле)
    NSMutableArray *arr = [self createArrayMRC];
    // Если мы хотим хранить его дальше, делаем retain
    [arr retain];
    // ... использование ...
    // Мы владеем, поэтому должны release
    [arr release];
}

Autorelease Pool

Autorelease pool был критически важным механизмом для временного управления объектами. В каждом цикле событий приложения создавался (часто неявно) пул. Объекты, отправленные в него через autorelease, получали отсроченный release в момент освобождения пула. Разработчики также могли создавать свои пулы для управления памятью в циклах, создающих много временных объектов.

// Создание своего autorelease pool для управления памятью в цикле
- (void)processManyItemsMRC {
    // Создаем локальный пул
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    for (int i = 0; i < 10000; i++) {
        // Временные объекты, создаваемые в цикле, будут autorelease в этот pool
        NSString *tempString = [NSString stringWithFormat:@"Item %d", i];
        // Использование tempString...
    }

    // Освобождаем пул, все временные объекты немедленно получают release
    [pool release];
    // Это предотвращает накопление памяти за время длинного цикла
}

Проблемы и переход к ARC

Ручное управление было чрезвычайно error-prone:

  • Утечки памяти: Если забыть release после retain или для объекта созданного через alloc.
  • Крахи: Если вызвать release слишком много раз или обратиться к объекту после его освобождения.
  • Сложность в больших проектах: Требовалась абсолютная дисциплина и глубокое понимание всех потоков владения объектами.

Введение ARC в 2011 году (с iOS 5 и Xcode 4.2) стало революцией. Компилятор Clang начал автоматически анализировать код и расставлять retain, release, autorelease в нужных местах, сохраняя семантику подсчета ссылок, но полностью устраняя ручную работу. Разработчик лишь указывал силу ссылок (strong, weak, unsafe_unretained), а компилятор генерировал корректный управляющий код. Это устранило огромный класс ошибок, сделав разработку на Objective-C и Swift значительно безопаснее и продуктивнее.