Почему @autoreleasepool все еще нужен с ARC?

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

@autoreleasepool {
    …
}

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


EDIT: подводя итог, что я получил все ответы и комментарии кратко:

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

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

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

главное:

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

7 ответов


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

одно из других изменений, которые они сделали с новым компилятором 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-loop, тогда вы будете выпускать 100000000 объекты позже, а не 10000 каждый раз вокруг внешней for-loop.

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

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


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

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


люди часто неправильно понимают ARC для какой-то сборки мусора или тому подобное. Правда в том, что через некоторое время люди в Apple (благодаря проектам llvm и clang) поняли, что Objective-c's memory administration (все retains и releases, etc.) может быть полностью автоматизирован в время компиляции. Это, просто прочитав код, еще до его запуска! :)

для этого есть только одно условие: мы должны следовать правила, в противном случае компилятор не сможет автоматизировать процесс компиляции. Итак, чтобы убедиться, что мы никогда нарушайте правила, нам не разрешается явно писать release, retain, etc. Эти вызовы автоматически вводятся в наш код компилятором. Следовательно, внутренне мы все еще имеем autoreleases,retain, release, etc. Просто нам больше не нужно их писать.

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

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


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


цитата из https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html:

блоки и потоки пула Autorelease

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

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

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

...

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


пулы Autorelease требуются для возврата вновь созданных объектов из метода. Е. Г. рассмотрим этот кусок кода:

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

строка, созданная в методе, будет иметь число сохранения одного. Так кто же уравновесит этот счет освобождением?

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

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

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

вот почему есть autorelease пулы, поэтому первый способ будет на самом деле стать

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

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

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

рассмотрим этот код дуги:

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

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

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

// 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 устраняет atuorelease, таким образом, также задержанный выпуск объекта при уничтожении пула, а также безопасность удерживать. Чтобы узнать больше о том, как это возможно и что на самом деле происходит за кулисами,проверить это сообщение в блоге.

теперь к фактическому вопросу: Зачем использовать @autoreleasepool?

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

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

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

если вы напишете код следующим образом:

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

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

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


по этой теме, похоже, много путаницы (и, по крайней мере, 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

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

причина, по которой @autoreleasepool все еще разрешен,-это смешанные проекты ARC и non-ARC, которые еще не полностью перешли на ARC.

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

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