Пулы автоматического выпуска требуются для возврата вновь созданных объектов из метода. Например, рассмотрим этот кусок кода:
- (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 ...
}
Затем каждый раз при запуске цикла for создается новый пул, который уничтожается в конце каждой итерации цикла. Таким образом, в любой момент времени в памяти висит не более одного временного объекта, несмотря на то, что цикл выполняется миллион раз.
В прошлом вам часто приходилось самостоятельно управлять автозапусками при управлении потоками (например, с использованием NSThread
), поскольку только основной поток автоматически имеет пул автозапуска для приложения Cocoa / UIKit. Однако сегодня это в значительной степени наследие, поскольку сегодня вы, вероятно, не будете использовать потоки для начала. Вы бы использовали GCD DispatchQueue
или NSOperationQueue
s, и оба они управляют пулом автоматического выпуска верхнего уровня для вас, созданным перед запуском блока / задачи и уничтоженным, как только закончите с ним.