Пример какао - почему нет необходимости сохранять или выпускать сообщения

Я новичок в Objective-C и какао. В руководстве Apple для Cocoa приведен запутанный пример управления памятью:

Предположим, вы хотите реализовать метод сброса счетчика. У вас есть пара вариантов. Первая реализация создает экземпляр NSNumber с помощью alloc, поэтому вы балансируете это с выпуском.

- (void)reset {
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

Второй использует удобный конструктор для создания нового объекта NSNumber. Поэтому нет необходимости сохранять или освобождать сообщения

- (void)reset {
    NSNumber *zero = [NSNumber numberWithInteger:0];
    [self setCount:zero];
}

Я не уверен, почему объект, созданный с помощью 'new' вместо 'alloc & init', не нужно сохранять / освобождать. Насколько я понимаю, оба делают одно и то же, за исключением того, что с помощью "alloc & init" мы можем использовать пользовательские проверки и инициализацию.

Большое спасибо.

3 ответа

Решение

Второй пример возвращает автоматически освобожденный объект. Код для удобного конструктора, вероятно, выглядит следующим образом, по крайней мере, когда речь идет о копировании функциональности.

+ (NSNumber *)numberWithInteger:(NSInteger)integer
{
    NSNumber *number = [[NSNumber alloc] initWithInteger:integer];
    return [number autorelease];
}

Авторелиз - это способ отложить отправку метода release для объекта, не делегируя право собственности на объект вызывающей стороне конструктора. Это важная концепция, поскольку соглашения об именах требуют, чтобы вы не возвращали право собственности на объект, если ваш метод не начинается с copynewalloc или же retain, Однако, так как вы не можете вернуть принадлежащий объект, вам придется вызвать release в вашем удобном конструкторе, который затем приведет к возврату освобожденного объекта. Таким образом, autorelease позволяет вам вернуть не принадлежащий объект вызывающему объекту, который получит реальный метод освобождения позже (когда текущий пул автоматического выпуска будет очищен).

Методы автоматического выпуска собираются в так называемые пулы автоматического выпуска, которые представляют собой локальные квазисвязанные списки потоков (они не реализованы как связанные списки, но они работают так, как они были), и которые просто собирают автоматически выпущенные объекты. Объекты могут быть добавлены к ним несколько раз, вызывая, один раз для каждого autorelease метод, который они получают. Когда бассейны будут осушены или уничтожены, все содержащиеся в нем объекты получат release сообщение. По умолчанию система предоставит вам пул автоматических выпусков, по крайней мере, в основном потоке, но вы можете создавать свои собственные, используя этот код (который также используется в каждом основном методе, если вы посмотрите):

@autoreleaspool
{
    [foo autorelease]; // foo will receive a `release` method at the closing brace    
}

По соглашению, все методы, имена которых начинаются с "alloc" или "copy" или "new", увеличивают количество сохраняемых данных на 1.

Все остальные методы не изменяют счет сохранения. Так как "numberWithInteger" не начинается с "alloc", "copy" или "new", он возвращает объект с сохраняемым счетчиком 0. Но вместо того, чтобы установить его в 0 немедленно (что никогда не будет работать), он устанавливает сохранение считать до 1, а затем планирует его сбросить до 0 в какой-то момент в будущем (обычно, когда цикл событий простаивает, но вы можете вручную сделать это раньше), используя "пул автообновления".

Единственное исключение из соглашения о присвоении / копировании / новом именовании - это, конечно, фактические методы "сохранить" и "разблокировать" и "автоматическое освобождение", которые увеличивают / уменьшают количество сохраняемых данных или планируют его дальнейшее уменьшение.

Это не языковая функция, это просто практика кодирования. Каждый метод, начинающийся с наборов "alloc" и "copy" и "new", сохраняет значение 1, все остальное устанавливает его на 1, но планирует, что он будет сброшен до 0 позже.

Конечно, если вы используете ARC, и вы обязательно должны использовать ARC, вам не нужно беспокоиться об этом, потому что компилятор найдет последнюю строку кода, которая использует объект, и вставит строку код после него, чтобы освободить память. Это приводит к более быстрому коду, потому что механизм автоматического выпуска является немного медленным и иногда использует слишком много оперативной памяти.

Когда ARC включен, ни одно из этих правил не применяется. Компилятор игнорирует весь ваш код управления памятью (или говорит вам удалить его) и записывает свой собственный код. Это не фактическая сборка мусора, это просто ручное управление памятью, которое генерируется XCode, а не пишется вручную.

Объекты, созданные с +alloc -init возвращаются с сохранением количества +1, что означает, что вы должны позвонить -release когда вы закончите с этим объектом. Это то же самое с +new, которая является простой комбинацией [[ alloc] init]например

NSDate *date = [NSDate new]; // I will have to send -release at some point

Как правило, методы, содержащие init, new, create и copy, передают вам право собственности, что означает, что вам нужно отправить -release в какой-то момент. Другие методы не будут передавать права собственности, что означает, что объект автоматически освобожден, т.е. он имеет короткий срок службы и в будущем должен быть ликвидирован. Если вы хотите, чтобы этот объект оставался без присмотра, вам нужно явно взять на себя ответственность, отправив -retain,

NSDate *date = [NSDate date]; // I won't have to send -release
// But this object will be dealloced soon
// so if I want it to stick around, I will need to retain it

Это соглашение не обеспечивается языком.

Также полезно помнить, что это соглашение выходит за рамки части Objective C в SDK, это также относится и к CoreGraphics и все платформы C, предоставляемые Apple, и большинство сторонних платформ используют это соглашение.

CGContextRef context = UIGraphicsGetCurrentContext(); // I won't have to release this context
CGImageRef result = CGBitmapContextCreateImage(context); // I will have to release this image
/* ... */
CGRelease(result);

Что касается вашего примера: -numberWithInteger: не содержит init, new, create или copy. Так что вам не нужно -release это когда сделано.

Другие вопросы по тегам