Пулы автоматического выпуска требуются для возврата вновь созданных объектов из метода.Например, рассмотрим этот фрагмент кода:
- (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
, и оба они управляют пулом автоматического выпуска верхнего уровня, созданным до запуска блока / задачи и уничтоженным после того, как с ним выполнено.