Розуміння підрахунку посилань з какао та «Ціль-С»


122

Я тільки починаю дивитися на Objective-C та какао з метою грати з iPhone SDK. Мені досить зручно з C mallocта freeконцепцією, але схема підрахунку посилань на какао мене досить заплутала. Мені кажуть, що це дуже елегантно, коли ти це зрозумієш, але я ще не перевернув горб.

Як release, retainі autoreleaseробота , і які угоди про їх використання?

(Або якщо цього не зробити, що ви прочитали, що допомогло вам отримати це?)

Відповіді:


148

Почнемо з retainі release; autoreleaseнасправді це лише окремий випадок, коли ви зрозумієте основні поняття.

У какао кожен об’єкт відстежує, скільки разів на нього посилаються (конкретно, NSObjectбазовий клас реалізує це). Зателефонувавши retainна об’єкт, ви повідомляєте йому, що хочете збільшити його посилання на одиницю. Зателефонувавши release, ви повідомляєте об'єкт, який ви його відпускаєте, і його кількість посилань зменшується. Якщо після виклику releaseкількість посилань зараз дорівнює нулю, пам'ять цього об’єкта звільняється системою.

Основний спосіб цього відрізняється mallocі freeполягає в тому, що будь-якому даному об'єкту не потрібно турбуватися про збої в інших частинах системи, оскільки ви звільнили пам'ять, яку вони використовували. Якщо припустити, що всі грають і зберігають / випускають згідно з правилами, коли один фрагмент коду зберігається, а потім випускає об'єкт, будь-який інший фрагмент коду, що також посилається на об'єкт, не вплине.

Що іноді може бентежити - це знати обставини, за яких вам слід зателефонувати retainта release. Моє загальне правило полягає в тому, що якщо я хочу повісити на об'єкт деякий проміжок часу (наприклад, якщо це змінна члена в класі), то мені потрібно переконатися, що кількість посилань об'єкта знає про мене. Як описано вище, кількість посилань об'єкта збільшується за допомогою виклику retain. За умовою, він також збільшується (встановлюється дійсно на 1), коли об’єкт створюється методом "init". В будь-якому з цих випадків моя відповідальність викликати releaseоб'єкт, коли я закінчую його використання. Якщо цього не зробити, відбудеться витік пам'яті.

Приклад створення об'єкта:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Тепер для autorelease. Автореліз використовується як зручний (а іноді і необхідний) спосіб сказати системі звільнити цей об’єкт через деякий час. З точки зору сантехніки, коли autoreleaseвикликається, поточний потік NSAutoreleasePoolповідомляється про виклик. NSAutoreleasePoolТепер знає , що коли - то він отримує можливість (після поточної ітерації циклу подій), він може зателефонувати releaseна об'єкті. З нашого погляду як програмістів, він піклується про releaseте, щоб зателефонувати до нас, тому нам не доведеться (а насправді цього не слід).

Важливо відзначити, що (знову ж таки, за умовою) всі методи класу створення об'єктів повертають об'єкт, що вийшов автоматично. Наприклад, у наступному прикладі змінна "s" має кількість відліку 1, але після завершення циклу подій вона буде знищена.

NSString* s = [NSString stringWithString:@"Hello World"];

Якщо ви хочете повісити на цю рядок, вам потрібно буде подзвонити retainявно, а потім явно, releaseколи закінчите.

Розгляньте наступний (дуже надуманий) біт коду, і ви побачите ситуацію, коли autoreleaseпотрібно:

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Я усвідомлюю, що все це дещо заплутано - хоча в якийсь момент воно натисне. Ось декілька посилань, які вам допоможуть:

  • Вступ Apple в управління пам'яттю.
  • Програмування какао для Mac OS X (4-е видання) , Аарон Хіллегас - дуже добре написана книга з великою кількістю чудових прикладів. Він читається як підручник.
  • Якщо ви по-справжньому занурюєтесь, ви можете відправитися на ранчо Big Nerd . Це навчальний заклад, яким керує Аарон Хіллегас - автор згаданої вище книги. Я там відвідував курс «Вступ до какао» кілька років тому, і це був чудовий спосіб вчитися.

8
Ви писали: "Зателефонувавши до автоматичного випуску, ми тимчасово збільшимо кількість посилань". Я думаю, що це неправильно; autorelease позначає лише об'єкт, який буде випущено в майбутньому, він не збільшує кількість посилань
LKM

2
"Тепер для автовипуску. Авторелізе використовується як зручний (а іноді і необхідний) спосіб сказати системі звільнити цей об'єкт через деякий час." Як вступне речення, це неправильно. Він не говорить системі "звільнити [вгору]", він повідомляє про зменшення кількості рахунків.
mmalc

3
Велике спасибі за гарне пояснення. Тільки одне, що досі незрозуміло. Якщо NSString* s = [[NSString alloc] initWithString:@"Hello World"];повертається автоматично випущений об’єкт (як ви його пишете), чому я повинен робити це return [s autorelease];і встановити його "автовипуск" ще раз, а не просто return s?
znq

3
@Stefan: [[NSString alloc] initWithString:@"Hello World"]НЕ поверне автоматично виданий об'єкт. Щоразу, коли allocвикликається, кількість посилань встановлюється на 1, і цей код повинен нести обов'язок. З [NSString stringWithString:]іншого боку, дзвінок повертає об'єкт , який автоматично вийшов.
Метт Діллард

6
Веселі дрібниці: Оскільки у відповіді використовуються @ "" та NSString, рядки є постійними протягом усього часу, і, таким чином, абсолютна кількість затримки буде одночасно постійною і абсолютно не має значення .... не робить відповідь помилковою, будь-якими способами, просто підкреслює той факт, що абсолютна кількість утримань ніколи насправді не є тим, про що слід хвилюватися.
bbum

10

Якщо ви розумієте процес утримання / випуску, то є два золотих правила, які "очевидні" для встановлених програмістів какао, але, на жаль, рідко це чітко прописано для новачків.

  1. Якщо функція , яка повертає об'єкт має alloc, createабо copyв його назві , то об'єкт належить вам. Ви повинні зателефонувати, [object release]коли закінчите з цим. Або CFRelease(object), якщо це об’єкт Core-Foundation.

  2. Якщо в його назві НЕ є одне із цих слів, то об'єкт належить комусь іншому. Ви повинні зателефонувати, [object retain]якщо хочете зберегти об'єкт після закінчення функції.

Вам було б добре дотримуватися цієї конвенції у створених вами функціях.

(Nitpickers: Так, на жаль, є кілька викликів API, які є винятками з цих правил, але вони рідкісні).


11
Це неповно і неточно. Я продовжую не розуміти, чому люди намагаються повторити правила, а не просто вказують на відповідну документацію: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc

4
Зокрема, основні правила Фонду відрізняються від правил какао; дивіться developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc

1
Я також не згоден. Якщо функція повертає те, чим не хоче володіти, вона повинна автоматично випустити її. Викликає завдання функції, щоб зберегти його (за бажанням). Він не повинен мати нічого спільного з назвою будь-якого методу, на який використовується. Це більше кодування C Style, коли право власності на об'єкти незрозуміле.
Сем

1
Вибачте! Я думаю, що я поспішав в голосуванні. Правила управління пам'яттю Ваша відповідь майже цитує яблучний документ.
Сем

8

Якщо ви пишете код на робочому столі, і ви можете націлити на Mac OS X 10.5, вам слід хоча б вивчити використання сміття Objective-C. Це дійсно спростить більшу частину вашої розробки - саме тому Apple доклала всіх зусиль, щоб створити її в першу чергу і змусити її добре працювати.

Що стосується правил управління пам'яттю, коли не використовується GC:

  • Якщо ви створюєте новий об'єкт , використовуючи +alloc/+allocWithZone:, +new, -copyабо -mutableCopyчи якщо ви -retainоб'єкт, ви приймаєте права на нього і повинен переконатися , що він прямує -release.
  • Якщо ви отримуєте об'єкт будь-яким іншим способом, ви не є його власником і не повинні забезпечувати його надсилання -release.
  • Якщо ви хочете переконатись, що об’єкт надісланий, -releaseви можете або надіслати його самостійно, або ви можете надіслати об’єкт, -autoreleaseі поточний пул автовипуску надішле його -release(один раз на отриманий -autorelease), коли пул буде злито.

Зазвичай -autoreleaseвикористовується як спосіб забезпечення того, що об’єкти живуть протягом тривалості поточної події, але після цього видаляються, оскільки існує пул автоматичних випусків, який оточує обробку подій Cocoa. У какао набагато частіше повертати об'єкти абоненту, які автоматично випускаються, ніж повертати об'єкти, які потрібно викликати.


6

Objective-C використовує підрахунок посилань , що означає, що кожен об'єкт має відліку. Коли об’єкт створений, він має відліку «1». Простіше кажучи, коли посилається на об'єкт (тобто зберігається десь), він стає "збереженим", що означає, що його кількість посилань збільшується на одиницю. Коли об'єкт більше не потрібен, він "вивільняється", що означає, що його контрольна кількість зменшується на одиницю.

Коли посилання на об'єкт дорівнює 0, об'єкт звільняється. Це основний довідковий підрахунок.

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

Типовим способом написання методу є:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Проблема необхідності пам’ятати, щоб випустити будь-які придбані ресурси всередині коду, є і стомлюючою, і помилковою. Objective-C представляє ще одну концепцію, спрямовану на те, щоб зробити це набагато простіше: Autorelease Pools. Пули автоматичного випуску - це спеціальні об'єкти, які встановлюються на кожну нитку. Вони досить простий клас, якщо ви шукаєте NSAutoreleasePool.

Коли об’єкт отримує надіслане на нього повідомлення про "автоматичну випуск", об’єкт буде шукати будь-які пули автовипуску, що сидять на стеці для цього поточного потоку. Він додасть об'єкт до списку як об’єкт для надсилання повідомлення "реліз" в якийсь момент майбутнього, що, як правило, при звільненні самого пулу.

Взявши код вище, ви можете переписати його, щоб він був коротшим і легшим для читання, сказавши:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

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

Сподіваємось, це допомагає. Стаття у Вікіпедії досить гарна щодо підрахунку посилань. Більш детальну інформацію про пули автоматичного випуску можна знайти тут . Також зауважте, що якщо ви будуєте для Mac OS X 10.5 і пізніших версій, ви можете сказати Xcode будувати з увімкненим збиранням сміття, що дозволяє повністю ігнорувати утримувати / випускати / автовипускати.


2
Це просто неправильно. Немає необхідності надсилати деякий випуск об'єкта чи автовипуск у будь-якому із наведених прикладів.
mmalc

6

Джошуа (№ 6591) - Збір сміття в Mac OS X 10.5 здається досить класним, але він недоступний для iPhone (або якщо ви хочете, щоб ваш додаток працював на версії Mac OS X до 10.5).

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


2
Цілком можливо написати гібридну основу, яка підтримує як GC, так і підрахунок посилань.
mmalc

6

Як і коли-небудь, коли люди починають намагатися переосмислити довідковий матеріал, вони майже незмінно отримують щось не так або надають неповний опис.

Apple надає повний опис системи управління пам’яттю Cocoa в Посібнику з програмування управління пам’яттю для какао , в кінці якого міститься короткий, але точний підсумок Правил управління пам’яттю .




2
Насправді це набагато кращий підсумок на одній сторінці: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Брайан Москау

6

Я не додаду до специфіки збереження / випуску, крім того, що ви можете подумати про скидання 50 доларів та отримання книги Hillegass, але я б настійно пропоную скористатися інструментами "Інструменти" дуже рано у розробці вашої програми (навіть вашої Перший!). Для цього запустіть-> Почніть з інструментів для виконання. Я б почав з "Лекс", який є лише одним із багатьох доступних інструментів, але допоможе показати вам, коли ви забули випустити. Це дуже непросто, скільки інформації вам буде представлено. Але ознайомтеся з цим підручником, щоб швидко встати та йти:
НАВЧАЛЬНІСТЬ КАКАО: ВІДКЛЮЧЕННЯ ПАМ'ЯТНИХ ВПЛИВ З ІНСТРУМЕНТАМИ

Насправді спроба змусити витік може бути кращим способом навчитися їх запобігати! Удачі ;)


5

Метт Діллард написав :

повернути [[s autorelease] release];

Автореліз не об'єкт зберігає. Автовипуск просто ставить його в чергу, щоб вийти пізніше. Ви не хочете там мати заяву про випуск.




4

Відповідь NilObject - це гарний початок. Ось додаткова інформація, що стосується керування пам’яттю вручну ( потрібно на iPhone ).

Якщо ви особисто alloc/initє об'єктом, він посилається на номер посилання 1. Ви несете відповідальність за прибирання після нього, коли це більше не потрібно, або зателефонувавши [foo release]або [foo autorelease]. випуск очищає його відразу, тоді як автоматична випуск додає об'єкт до пулу автовипуску, який автоматично випустить його в подальшому.

autorelease - це перш за все, коли у вас є метод, який повинен повернути розглянутий об'єкт ( тому ви не можете його вручну звільнити, інакше ви повернете нульовий об'єкт ), але ви також не хочете його тримати .

Якщо ви придбаєте об'єкт, де ви не викликали alloc / init, щоб отримати його - наприклад:

foo = [NSString stringWithString:@"hello"];

але ви хочете повісити на цей об’єкт, вам потрібно зателефонувати [foo retain]. В іншому випадку можливо, він отримає, autoreleasedі ви будете триматися за нульову посилання (як це було б у наведеному вище stringWithStringприкладі ). Коли вам це більше не потрібно, телефонуйте [foo release].


2

Наведені вище відповіді дають чіткі перегляди того, що йдеться в документації; Проблема, з якою стикаються більшість нових людей, - це незадокументовані випадки. Наприклад:

  • Автовипуск : Документи кажуть, що це спровокує випуск "у якийсь момент у майбутньому". КОЛИ?! В основному, ви можете розраховувати на те, що навколо вас знаходиться об'єкт, поки не вийдете з коду назад у цикл системних подій. Система МОЖЕ звільнити об'єкт будь-коли після поточного циклу подій. (Я думаю, що Метт сказав це раніше.)

  • Статичні рядки : NSString *foo = @"bar";- чи потрібно це зберегти чи випустити? Ні про що

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • Правило створення : Якщо ви створили його, ви володієте ним і, як очікується, випустите його.

Взагалі, спосіб, коли нові програмісти какао заплутаються, - це не розуміючи, які підпрограми повертають об'єкт з а retainCount > 0.

Ось фрагмент від дуже простих правил управління пам’яттю в какао :

Правила утримання

  • У межах заданого блоку використання -copy, -loloc та -retain повинно дорівнювати використанню -release та -autorelease.
  • Об'єкти, створені за допомогою конструкторів зручності (наприклад, рядок NSStringWWSString), вважаються автоматично випущеними.
  • Реалізуйте метод -dealloc, щоб звільнити ваші власні екземпляри

Перша куля говорить: якщо ви подзвонили alloc(абоnew fooCopy ), вам потрібно зателефонувати до випуску цього об’єкта.

Друга куля говорить: якщо ви користуєтеся конструктором зручності і вам потрібен предмет, щоб зависати (як із зображенням, яке буде намальовано пізніше), вам потрібно зберегти (а потім пізніше випустити) його.

Третій повинен бути роз'яснювальним.


"Автореліз. Документи кажуть, що це спричинить випуск" у якийсь момент у майбутньому. КОЛИ ?! " Документи зрозуміли з цього приводу: "автореліз означає лише" надіслати повідомлення про випуск пізніше "(для певного визначення пізніше - див." Пули автоматичної випуску ")." Точно коли залежить від стеку пулу автоматичних
випусків

... "Система МОЖЕ звільнити об'єкт будь-коли після поточного циклу подій." Це робить звук системи досить менш детермінованим, ніж це ...
mmalc

... NSString foo = [self getBar]; // все ще не потрібно зберігати чи звільняти Це неправильно. Хто не звертається до getBar, не знає деталей реалізації, тому * повинен зберігати / випускати (як правило, через аксесуари), якщо вони хочуть використовувати його поза поточним діапазоном.
mmalc

Стаття "Дуже прості правила управління пам’яттю в какао" в багатьох аспектах застаріла - зокрема, "Об'єкти, створені за допомогою конструкторів зручності (наприклад, рядок NSStringWithString), вважаються випущеними автоматично". не правильно - він просто "не належить одержувачу".
mmalc


0

Як уже згадувалося декілька людей, Apple Intro to Memory Memory - це найкраще місце для початку.

Одне корисне посилання, про яке я ще не бачив, - це Практичне управління пам’яттю . Ви знайдете його посеред документів Apple, якщо прочитаєте їх, але варто прямо посилатися. Це геніальне резюме правил управління пам’яттю з прикладами та поширеними помилками (в основному те, що інші відповіді тут намагаються пояснити, але не так).

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