Чому @autoreleasepool все ще потрібен ARC?


191

Здебільшого з ARC (автоматичним підрахунком посилань) нам взагалі не потрібно думати про управління пам’яттю з об’єктами Objective-C. Створювати NSAutoreleasePools більше заборонено , проте є новий синтаксис:

@autoreleasepool {
    
}

Моє запитання: чому мені це коли-небудь знадобиться, коли я не повинен бути вручну випускати / авторелізинг?


EDIT: Щоб підсумувати те, що я отримав із усіх коментарів і коментарів, лаконічно:

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

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

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

[pool drain];

Що ще більш важливо:

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

1
Існує також третій випадок: коли ви розробляєте щось, що не пов'язане з UIKit або NSFoundation. Щось, що використовує інструменти командного рядка чи так
Гарник,

Відповіді:


215

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петлі.

Оновлення: також дивіться цю відповідь - https://stackoverflow.com/a/7950636/1068248 - адже чому @autoreleasepoolнемає нічого спільного з ARC.

Оновлення: я заглянув у внутрішні місця того, що відбувається тут, і написав це у своєму блозі . Якщо ви подивитесь там, то ви точно побачите, що робить ARC і як новий стиль @autoreleasepoolі як він вводить область використовується компілятором для отримання інформації про те, що потрібно зберігати, випускати та автовипускати.


11
Це не позбавляється від утримувань. Це додає їх вам. Підрахунок посилань все ще триває, він просто автоматичний. Звідси автоматичний підрахунок посилань :-D.
mattjgalloway

6
То чому він не додається і @autoreleasepoolдля мене? Якщо я не контролюю те, що отримується автоматично або випущено (ARC робить це для мене), як я повинен знати, коли створити пул автовипуску?
mk12

5
Але ви маєте контроль над тим, куди все ще зберігаються пули автоматичних випусків. Навколо вашого цілого додатка за замовчуванням є один, але ви можете хотіти ще.
mattjgalloway

5
Гарне питання. Ви просто повинні "знати". Подумайте, як додати його як подібне до того, чому ви можете, мовою GC, додати підказку до сміттєзбірника, щоб продовжувати та запускати цикл збирання зараз. Можливо, ви знаєте, що є маса об'єктів, готових очистити, у вас є цикл, який виділяє купу тимчасових об'єктів, тож ви "знаєте" (або Інструменти можуть сказати вам :), що додавати пул випуску навколо циклу було б гарна ідея.
Грем Перкс

6
Приклад циклічного циклу працює ідеально без автоматичного випуску: кожен об'єкт розміщується, коли змінна виходить за межі області. Запуск коду без автоматичного випуску займає постійний об'єм пам'яті та показує повторне використання покажчиків, а наведення точки розриву на діловий об'єкт показує, що він викликається один раз через цикл, коли викликається objc_storeStrong. Можливо, OSX робить щось тут німе, але autoreleasepool абсолютно непотрібний в iOS.
Гленн Мейнард

16

@autoreleasepoolнічого не випускає автоматично. Він створює пул автоматичного випуску, так що коли досягне кінець блоку, будь-які об'єкти, які були автоматично випущені ARC, поки блок був активним, будуть надсилатися повідомлення про випуск. Розширений посібник з програмування управління пам'яттю Apple пояснює це так:

Наприкінці блоку пулу автоматичної випуску об’єкти, які отримали повідомлення про автоматичну звільнення всередині блоку, надсилають повідомлення про випуск - об’єкт отримує повідомлення про звільнення кожного разу, коли воно надсилається повідомлення про автоматичну випуску в межах блоку.


1
Не обов'язково. Об'єкт отримає releaseповідомлення, але якщо кількість збереження становить> 1, об'єкт НЕ буде розміщений.
andybons

@andybons: оновлено; Дякую. Це зміна від поведінки до ARC?
outis

Це неправильно. Об'єкти, випущені ARC, надсилатимуть повідомлення про випуск, як тільки вони будуть випущені ARC, з пулом автоматичного випуску або без нього.
Гленн Мейнард

7

Люди часто неправильно розуміють АРК за якесь збирання сміття тощо. Правда полягає в тому, що через деякий час люди в Apple (завдяки проектам llvm та clang) зрозуміли, що адміністрування пам'яті Objective-C (все retainsта releasesін.) Може бути повністю автоматизовано під час компіляції . Це, лише читаючи код, ще до його запуску! :)

Для цього існує лише одна умова: МОЖЕ дотримуватися правил , інакше компілятор не зможе автоматизувати процес під час компіляції. Таким чином, щоб гарантувати , що ми ніколи не порушувати правила, ми не маємо права явно записи release, retainі т.д. Ці виклики автоматично впорскується в наш код компілятором. Тому всередині у нас ще є autoreleaseз, retain, releaseі т.д. Це просто нам не потрібно писати їх більше.

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

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


1
ARC посилається на підрахунок GC, а не на маркування GC, як ви отримуєте в JavaScript та Java, але це, безумовно, збирання сміття. Це не стосується питання - "ви можете" не відповідає на питання "навіщо вам". Ви не повинні.
Гленн Мейнард

3

Це тому, що вам все-таки потрібно надати компілятору підказки про те, коли безпечно для автоматично випущених об'єктів вийти за межі області.


Чи можете ви надати мені приклад того, коли вам потрібно було б це зробити?
mk12


Тому перед ARC, наприклад, у мене був CVDisplayLink, що працює на другому потоці для мого додатка OpenGL, але я не створив пул автоматичних випусків у його запуску, тому що я знав, що нічого не використовую (або використовую бібліотеки). Це означає, що зараз мені потрібно додати, @autoreleasepoolоскільки я не знаю, чи може ARC вирішити щось автовипустити?
mk12

@ Mk12 - Ні. Ви завжди матимете пул автоматичних випусків, який щоразу витікає навколо основного циклу запуску. Додавати його потрібно лише тоді, коли ви хочете переконатись, що об'єкти, що були автоматично випущені, виснажуються ще до того, як вони інакше - наприклад, наступного разу, коли оберіть цикл запуску.
mattjgalloway

2
@DougW - Я ознайомився з тим, що насправді робить компілятор, і про це розповів тут - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Сподіваємось, пояснюється, що відбувається як під час компіляції, так і під час виконання.
mattjgalloway

2

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

Блоки та нитки автоматичного випуску

Кожна нитка в програмі Cocoa підтримує власний пакет блоків пулу автоматичних випусків. Якщо ви пишете програму, що стосується лише Фонду, або ви від'єднуєте нитку, вам потрібно створити власний блок пулу автоматичних випусків.

Якщо ваша програма або потік довговічні і потенційно генерують багато об'єктів, що автоматично випускаються, вам слід використовувати блоки пулу автовипуску (наприклад, AppKit і UIKit робити в основному потоці); інакше об'єкти, що автоматично випускаються, накопичуються і слід пам'яті зростає. Якщо ваша відокремлена нитка не здійснює дзвінки з какао, вам не потрібно використовувати блок пулу автоматичного випуску.

Примітка. Якщо ви створюєте вторинні потоки, використовуючи API ниток POSIX замість NSThread, ви не можете використовувати какао, якщо какао не перебуває в режимі багатопотокового прочитання. Какао переходить у багатопотоковий режим лише після від'єднання першого об'єкта NSThread. Щоб використовувати какао на вторинних потоках POSIX, ваша програма спочатку повинна від'єднати принаймні один об'єкт NSThread, який може негайно вийти. Ви можете перевірити, чи перебуває какао в режимі багатопотокового редагування методом класу NSThread isMultiThreaded.

...

В системі автоматичного підрахунку довідок або ARC система використовує ту саму систему підрахунку опор, що і MRR, але вона вставляє відповідний метод управління пам'яттю, який викликає вас під час компіляції. Вам настійно рекомендується використовувати ARC для нових проектів. Якщо ви використовуєте ARC, зазвичай не потрібно розуміти основну реалізацію, описану в цьому документі, хоча в деяких ситуаціях це може бути корисним. Докладніше про ARC див. У розділі Перехід до приміток до випуску ARC.


2

Пули автоматичного випуску необхідні для повернення новостворених об'єктів із методу. Наприклад, врахуйте цей фрагмент коду:

- (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, і вони обидва управляють пулом автоматичного випуску верхнього рівня для вас, створеним перед запуском блоку / завдання та знищеним після завершення роботи з ним.


-4

На цю тему, мабуть, існує велика плутанина (і принаймні 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, то забудьте про авторелізпул.


4
Ця відповідь неправильна, а також суперечить документації ARC. ваші докази є анекдотичними, оскільки ви, звичайно, використовуєте метод розподілу, який компілятор вирішив не випускати автоматично. Ви можете легко побачити, що це не працює, якщо створити новий статичний ініціалізатор для власного класу. Створити цей ініціалізатор і використовувати його в циклі: + (Testing *) testing { return [Testing new] }. Тоді ви побачите, що угоду не дзвонять, поки пізніше. Це виправлено, якщо ви загорнуте внутрішню частину петлі в @autoreleasepoolблок.
Діма

@Dima Спробував на iOS10, зволікайте до оператора відразу після друку адреси об'єкта. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC

@KudoCC - Так я і я побачив таку ж поведінку, що і ти. Але, коли я кинув [UIImage imageWithData]у рівняння, раптом я почав бачити традиційну autoreleaseповедінку, вимагаючи @autoreleasepoolзберегти пік пам’яті до якогось розумного рівня.
Роб

@Rob Я не можу допомогти собі додати посилання .
KudoCC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.