Почему @autoreleasepool все еще нужен для ARC? - PullRequest
188 голосов
/ 01 февраля 2012

По большей части с ARC (Автоматический подсчет ссылок) нам вообще не нужно думать об управлении памятью с объектами Objective-C. Больше не разрешено создавать NSAutoreleasePool s, однако есть новый синтаксис:

@autoreleasepool {
    …
}

У меня вопрос: зачем мне это вообще нужно, если я не должен вручную выпускать / автоматически выпускать?


РЕДАКТИРОВАТЬ: Подводя итог, я получил кратко из всех ответов и комментариев:

Новый синтаксис:

@autoreleasepool { … } - новый синтаксис для

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];

Более важно:

  • ARC использует autorelease, а также release.
  • Для этого требуется пул автоматических выпусков.
  • ARC не создает пул автоматического выпуска для вас. Тем не менее:
    • В основном потоке каждого приложения Какао уже есть пул авто-релиза.
  • Есть два случая, когда вы захотите использовать @autoreleasepool:
    1. Когда вы находитесь во вторичном потоке и нет пула автоматического выпуска, вы должны создать свой собственный для предотвращения утечек, например myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Когда вы хотите создать более локальный пул, как показал @mattjgalloway в своем ответе.

Ответы [ 7 ]

208 голосов
/ 01 февраля 2012

ARC не избавляется от удержаний, выпусков и авто-выпусков, он просто добавляет нужные вам. Таким образом, все еще существуют вызовы для сохранения, есть вызовы для освобождения, все еще есть вызовы для автоматического выпуска и все еще существуют пулы автоматического выпуска.

Одно из других изменений, внесенных с помощью нового компилятора Clang 3.0 и ARC, заключается в том, что они заменили NSAutoReleasePool директивой компилятора @autoreleasepool. NSAutoReleasePool всегда был чем-то вроде особого «объекта», и они сделали это так, чтобы синтаксис его использования не путался с объектом, так что он, как правило, немного проще.

Итак, в общем, вам нужно @autoreleasepool, потому что есть еще пулы автоматических выпусков, о которых нужно беспокоиться. Вам просто не нужно беспокоиться о добавлении в autorelease звонков.

Пример использования пула автоматического выпуска:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

Конечно, очень надуманный пример, но если бы у вас не было @autoreleasepool внутри внешнего for -цикла, то вы бы выпустили 100000000 объектов позже, а не 10000 каждый раз вокруг внешнего for -loop.

Обновление: Также посмотрите этот ответ - https://stackoverflow.com/a/7950636/1068248 - почему @autoreleasepool не имеет ничего общего с ARC.

Обновление: Я взглянул на внутреннюю суть происходящего здесь и написал это в своем блоге . Если вы посмотрите туда, то увидите, что именно делает ARC и как новый стиль @autoreleasepool и как он вводит область видимости используется компилятором для вывода информации о том, что требуется, релизы и авто-релизы требуются.

14 голосов
/ 01 февраля 2012

@autoreleasepool ничего не выпускает автоматически. Он создает пул автоматического выпуска, поэтому, когда достигнут конец блока, любые объекты, которые были автоматически освобождены ARC, когда блок был активен, будут отправлены сообщения об освобождении. В руководстве Apple по расширенному управлению памятью это объясняется так:

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

7 голосов
/ 13 марта 2013

Люди часто неправильно понимают ARC для какой-то сборки мусора или тому подобного.Правда в том, что через некоторое время люди в Apple (благодаря проектам llvm и clang) осознали, что администрирование памяти Objective-C (все retains и releases и т. Д.) Может быть полностью автоматизировано во время компиляции .Это, просто читая код, даже до его запуска!:)

Для этого существует только одно условие: мы ДОЛЖНЫ следовать правилам , иначе компилятор не сможет автоматизировать процесс во время компиляции.Итак, чтобы гарантировать, что мы никогда не нарушаем правила, нам не разрешается явно писать release, retain и т. Д. Эти вызовы автоматически вводятся в наш код компилятором.Следовательно, внутренне у нас все еще есть autorelease s, retain, release и т. Д. Просто нам больше не нужно их писать.

A ARC автоматически во время компиляции, чтогораздо лучше, чем во время выполнения, например, сборщик мусора.

У нас все еще есть @autoreleasepool{...}, потому что наличие этого не нарушает ни одного из правил, мы можем свободно создавать / удалять наш пул в любое время, когда нам это нужно:).

3 голосов
/ 01 февраля 2012

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

1 голос
/ 18 ноября 2014

Цитируется из https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html:

Автозапуск блоков и потоков пула

Каждый поток в приложении Какао поддерживает свой собственный стек блоки автозапуска бассейна. Если вы пишете программу только для Фонда или если вы отсоединили тему, вам нужно создать свой собственный автоматический выпуск блок бассейна.

Если ваше приложение или поток долгоживущий и потенциально генерирует много автоматически выпущенных объектов, вы должны использовать блоки пула автоматического освобождения (как AppKit и UIKit делают в основном потоке); в противном случае, авто-релиз объекты накапливаются и ваш след памяти растет. Если вы отстранены поток не делает звонки Какао, вам не нужно использовать блок автозапуска пула.

Примечание. Если вы создаете вторичные потоки с помощью API потоков POSIX вместо NSThread вы не можете использовать Какао, если Какао не находится в многопоточный режим. Какао входит в многопоточность только после отсоединение своего первого объекта NSThread. Использовать Какао на вторичном POSIX потоки, ваше приложение должно сначала отсоединить хотя бы один NSThread объект, который может сразу выйти. Вы можете проверить, есть ли Какао в Режим многопоточности с методом класса NSThread isMultiThreaded.

...

При автоматическом подсчете ссылок или ARC система использует тот же система подсчета ссылок как MRR, но она вставляет соответствующую память метод управления вызывает вас во время компиляции. Вы сильно Рекомендуется использовать ARC для новых проектов. Если вы используете ARC, есть как правило, нет необходимости понимать основную реализацию описано в этом документе, хотя в некоторых ситуациях полезно. Для получения дополнительной информации о ARC см. Переход к примечаниям к выпуску ARC.

0 голосов
/ 09 августа 2018

Пулы автоматического выпуска требуются для возврата вновь созданных объектов из метода.Например, рассмотрим этот фрагмент кода:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

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

Сам метод?Невозможно, он должен вернуть созданный объект, поэтому он не должен выпускать его до возвращения.

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

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

Вот почему существуют пулы авто-выпуска, поэтому первым методом на самом деле станет

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

Вызов autorelease объекта добавляет его в пул авто-выпуска, но что это действительно означает?, добавление объекта в пул автоматического выпуска?Что ж, это значит сказать вашей системе " Я хочу, чтобы вы выпустили этот объект для меня, но через некоторое время, а не сейчас; у него есть счет сохранения, который должен быть уравновешен выпуском, иначе память утечет, но я не могусделай это сам сейчас, так как мне нужно, чтобы объект остался живым за пределами моей текущей области видимости, и мой вызывающий не сделает этого и для меня, он не знает, что это нужно сделать, поэтому добавь его в свой пул и как только тыочистить этот пул, а также очистить мой объект для меня."

С ARC компилятор решает за вас, когда сохранить объект, когда освободить объект и когда добавить его в пул авто-выпуска.но для этого все еще требуется наличие пулов автоматического выпуска, чтобы иметь возможность возвращать вновь созданные объекты из методов без утечки памяти.Apple только что провела несколько оптимизаций в сгенерированном коде, которые иногда устраняют пулы автоматического выпуска во время выполнения.Эта оптимизация требует, чтобы и вызывающий, и вызывающий использовали ARC (помните, что смешивание ARC и non-ARC допустимо и также официально поддерживается), и если это действительно так, то это можно узнать только во время выполнения.

Рассмотримэтот код ARC:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

Код, который генерирует система, может вести себя как следующий код (это безопасная версия, которая позволяет свободно смешивать код ARC и код, не являющийся ARC):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Обратите внимание, что сохранение / освобождение в вызывающей программе является просто защитным сохранением, оно не является строго обязательным, код был бы совершенно правильным без него)

Или он может вести себя как этот код,в случае, если оба обнаруживают использование ARC во время выполнения:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

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

Теперь перейдем к актуальному вопросу: зачем использовать @autoreleasepool?

Для большинства разработчиков сегодня есть только одна причина использовать эту конструкцию в своем коде, а именно, уменьшить объем памяти, где это применимо.Например, рассмотрим этот цикл:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Предположим, что при каждом вызове tempObjectForData может создаваться новый TempObject, который возвращается авто-релизом.Цикл for создаст миллион таких временных объектов, которые все будут собраны в текущем автозапуске, и только после уничтожения этого пула все временные объекты также будут уничтожены.Пока это не произойдет, у вас в памяти будет миллион таких временных объектов.

Если вместо этого вы напишите такой код:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

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

В прошлом вам часто приходилось самостоятельно управлять автообновлением при управлении потоками (например, с помощью NSThread).поскольку только основной поток автоматически имеет пул автоматического выпуска для приложения Cocoa / UIKit.Однако сегодня это в значительной степени наследие, поскольку сегодня вы, вероятно, не будете использовать потоки для начала.Вы бы использовали GCD DispatchQueue или NSOperationQueue, и оба они управляют пулом автоматического выпуска верхнего уровня, созданным до запуска блока / задачи и уничтоженным после того, как с ним выполнено.

0 голосов
/ 25 февраля 2014

Похоже, что в этой теме много путаницы (и, по крайней мере, 80 человек, которые, вероятно, сейчас смущены этим и думают, что им нужно посыпать @autoreleasepool вокруг своего кода).

Если проект (включая его зависимости) исключительно использует ARC, тогда @autoreleasepool не нужно использовать и ничего полезного не сделает.ARC будет обрабатывать освобождение объектов в нужное время.Например:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

отображает:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

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

Причина, по которой @autoreleasepool по-прежнему разрешена, относится к смешанным ARC и неПроекты -ARC, которые еще не полностью перешли на ARC.

Если вы вызываете код, не относящийся к ARC, он может возвращать объект с автоматическим освобождением.В этом случае вышеприведенный цикл будет протекать, так как текущий пул автоматического выпуска никогда не будет завершен.Вот где вы хотели бы поместить @autoreleasepool вокруг блока кода.

Но если вы полностью выполнили переход ARC, тогда забудьте об autoreleasepool.

...