Пули автоматичного випуску необхідні для повернення новостворених об'єктів із методу. Наприклад, врахуйте цей фрагмент коду:
- (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 є законним, а також офіційно підтримується), і якщо це насправді, то можна знати лише під час виконання.
Розглянемо цей код АРК:
// 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 усуває atuorelease, таким чином, також затримку випуску об'єкта при знищенні пулу, а також збереження безпеки. Щоб дізнатися більше про те, як це можливо і що насправді відбувається за лаштунками, перегляньте цю публікацію в блозі.
Тепер до актуального питання: навіщо використовувати один @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
's або NSOperationQueue
' s, і вони обидва управляють пулом автоматичного випуску верхнього рівня для вас, створеним перед запуском блоку / завдання та знищеним після завершення роботи з ним.