Objective - C отсчет ссылок, когда указатель установлен в ноль (без дуги)


Я пытаюсь понять, как работает подсчет ссылок, поэтому отключил ARC и написал простой класс: (Foo.h не вставляется, так как он немодифицирован)

Фу.m

@implementation Foo

- (instancetype)init
{
    NSLog(@"Init object");
    return [super init];
}

- (void)dealloc
{
    NSLog(@"Dealloc object");
    [super dealloc];
}

@end

Главная.m

#import <Foundation/Foundation.h>
#import "Foo.h"

int main(int argc, const char * argv[]) {

    Foo *obj = [[Foo alloc] init];

    obj = nil;
    return 0;
}
Теперь я ожидаю увидеть журнал dealloc object, потому что единственная ссылка на объект Foo исчезла, но единственное сообщение, которое я получаю, - это init object.

Почему я не вижу его? Разве объект не освобождается, когда я назначаю obj = nil?

2 2

2 ответа:

Нет. Если вы не используете ARC, объект освобождается при вызове [obj release];. (ARC вставляет эти вызовы для вас.) Установка obj в nil ничего не делает с точки зрения управления памятью (хотя и создает объект, до которого вы больше не можете добраться!).

В основном, в какао без дуги:

  • вы вызываете [obj retain], Если хотите стать владельцем объекта. (alloc делает это для вас.)
  • вы вызываете [obj release], когда хотите отказаться от права собственности на объект. release в свою очередь вызывает dealloc когда счетчик удержания объекта достигает 0.
  • вы вызываете [obj autorelease], когда хотите отказаться от владения объектом вне его текущей области действия. Чаще всего это происходит, когда вы возвращаете объект из метода (и не хотите сохранять право собственности на него).

Пара дополнительных наблюдений, чтобы расширить превосходный ответ мипади:

  1. В коде без дуги установка переменной в nil не будет освобождать объект. Вы должны явным образом release/autorelease это.

    Чтобы быть более точным об этом, если право собственности было передано вам (то есть вы получаете объект от метода, имя которого начинается с любого alloc, new, copy, или mutableCopy), то вы несете ответственность за явный вызов release/autorelease.

    Суть в том, что просто установить переменную в nil недостаточно.
  2. Я не решаюсь упомянуть об этом, опасаясь затуманить проблему, но когда речь идет о свойствах, это немного отличается. При использовании свойств вызов задатчика свойства retain автоматически создаст объект retain для вас. Если вы позже установите свойство в nil, то сеттер будет автоматически release его для вас.

    Представьте себе класс, подобный Итак:

    @interface Bar : NSObject
    @property (nonatomic, retain) Foo *foo;
    @end
    

    Если вы хотите установить свойство foo, вы можете сделать следующее

    Foo *f = [[Foo alloc] init];  // create Foo object with +1 retain count
    self.foo = f;                 // the `retain` property will increase retain count to +2
    [f release];                  // resolve the local strong reference, reducing retain count back to +1
    

    Или, проще говоря:

    self.foo = [[[Foo alloc] init] autorelease];
    

    Что они делают, так это создают объект Foo с числом удержания +1. Задавая свойство foo, вы создаете другую ссылку strong и, таким образом, получаете число удержания +2. А когда ты тогда ... release/autorelease это, число удержания падает обратно до +1.

    Позже, когда вы закончите с foo и захотите освободить его, вы просто вызовете снова сеттер, на этот раз со значением nil:

    self.foo = nil;              // resolve the `retain` done by the property, reducing the retain count to +0
    
    Это снимает сильную ссылку, которую свойство fooсохраняет на объект. Вы не звоните release себе (по крайней мере, в свойстве). Очевидно, что когда вы вызываете сеттер со значением nil, предыдущий объект не только освобождается,но если это была последняя сильная ссылка, объект будет освобожден и для вас.

Короче говоря, исходное alloc смещено на a release/autorelease, не от кого бы то ни было установка переменной в nil. Тем не менее, установка свойства retain разрешена путем установки этого свойства вnil.


Пара заключительных замечаний.
  • Я бы предложил, чтобы при написании кода без дуги вы внимательно изучили руководство по программированиюAdvanced Memory Management, особенно главуMemory Management Policy .

  • Статический анализатор Xcode (сдвиг+команда+B , или "Analyze "в меню" Product " Xcode) отлично справляется с выявлением проблем с памятью, которые беспокоят код без дуги. Всегда убедитесь, что у вас нет абсолютно никаких предупреждений от статического анализатора.

  • При использовании инструментов инструмент распределения имеет функцию, называемую "учет отсчетов ссылок" (см. https://stackoverflow.com/a/14105056/1271826 ). если вы обнаруживаете, что какой-то объект не освобождается, этот инструмент может быть полезно при диагностике полного жизненного цикла объекта и всех его сохраняемых отсчетов.