Чому в Objective-C я повинен перевіряти, чи self = [super init] не є нульовим?


165

У мене загальне запитання щодо написання методів init в Objective-C.

Я бачу всюди (код Apple, книги, відкритий вихідний код тощо), що метод init повинен перевірити, чи self = [super init] не є нульовим, перш ніж продовжувати ініціалізацію.

Типовим шаблоном Apple для методу init є:

- (id) init
{
    self = [super init];

    if (self != nil)
    {
        // your code here
    }

    return self;
}

Чому?

Я маю на увазі, коли init коли-небудь поверне нуль? Якщо я зателефонував до init на NSObject і повернувся до нуля, то щось має бути справді накрученим, правда? І в цьому випадку ви можете навіть не написати програму ...

Чи дійсно так часто, що метод init класу може повернути нуль? Якщо так, то в якому випадку і чому?


1
Віл Шиплі опублікував статтю, пов’язану з цим, ще раз. [self = [дурний ініт ];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) Прочитайте також коментарі, якісь хороші речі.
Райан Тауншенд

6
Ви можете запитати Віла Шиплі або Майка Еша чи Метта Галлахера . Так чи інакше, це щось із дискусійних тем. Але зазвичай добре дотримуватися ідіоми Apple ... зрештою, це їх Frameworks.
jbrennan

1
Здається, Віл робив справу більше, щоб не сліпо перепризначати себе під час init, знаючи, що [super init] може не повернути приймач.
Jasarien

3
Віл змінив свої думки з того часу, як цей пост був спочатку зроблений.
bbum

5
Це питання я бачив деякий час тому і просто знайшов його знову. Ідеально. +1
Dan Rosenstark

Відповіді:


53

Наприклад:

[[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"];

... і так далі. (Примітка: NSData може викинути виняток, якщо файл не існує). Існує досить багато областей, де повернення нуля - це очікувана поведінка, коли виникає проблема, і тому це стандартна практика весь час перевіряти наявність нуля заради послідовності.


10
Так, але це не ВНІШТЕ метод init відповідного класу. NSData успадковує від NSObject. Чи перевіряє NSData, якщо [super init] повертає нуль? Це я прошу тут. Вибачте, якщо мені не було зрозуміло ...
Jasarien

9
Якби ви підкласирували ці класи, є дуже реальний шанс, що [super init] поверне нуль. Не всі класи є прямими підкласами NSObject. Ніколи не існує гарантії, що вона не поверне нуль. Це, як правило, оборонна практика кодування.
Чак

7
Я сумніваюся, NSObject init може повернути нуль у будь-якому випадку, будь-коли. Якщо у вас немає пам’яті, аллок вийде з ладу, але якщо це вдасться, я сумніваюся, ініт може вийти з ладу - NSObject навіть не має змінних екземплярів, крім класу. У GNUStep його реалізовано як просто "повернення", а розбирання на Mac виглядає так само. Все це, звичайно, непотрібно - просто дотримуйтесь стандартної ідіоми, і вам не доведеться турбуватися про те, може вона чи не може.
Пітер Н Льюїс

9
Я не пекло нахилений не дотримуватися кращих практик. Мені хотілося б, однак, знати, чому вони найкращі практики в першу чергу. Начебто сказано зістрибнути з башти. Ви не просто продовжуєте це робити, якщо не знаєте чому. Чи є велика м'яка подушка, щоб приземлитися внизу, з величезною грошовою винагородою? Якби я знав, що стрибну. Якби ні, я б не став. Я не хочу сліпо слідувати практиці, не знаючи, чому я її дотримуюся ...
Jasarien

4
Якщо Aloc повертає нуль, init надсилається до нуля, що завжди призведе до нуля, і це закінчується тим, що "self" дорівнює нулю.
Т.

50

Ця ідіома є стандартною, оскільки вона працює у всіх випадках.

Нечасто, але бувають випадки, коли ...

[super init];

... повертає інший екземпляр, вимагаючи присвоїти себе.

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

Суть полягає в тому, що це задокументована правильна схема використання, і якщо ви її не використовуєте, ви робите це неправильно.


3
Чи все-таки це справедливо з огляду на специфікатори нульовості? Якщо ініціалізатор мого суперкласу не є нульовим, то чи справді все-таки варто зайвих клопотів перевірити? (Хоча сам NSObject, здається, не має жодних -initфактів ...)
natevw

Чи буває колись випадок, коли [super init]повертає нуль, коли прямий суперклас NSObject? Це не той випадок, коли "все порушено?"
Дан Розенстарк

1
@DanRosenstark Не, якщо NSObjectце прямий суперклас. Але ... навіть якщо ви заявляєте NSObjectяк прямий суперклас, щось можна було змінити під час виконання таким чином, що NSObjectреалізація initне є тим, що насправді називається.
bbum

1
Велике спасибі @bbum, це дійсно допомогло мені зорієнтуватися у вирішенні помилок. Добре виключати деякі речі!
Дан Розенстарк

26

Я думаю, що в більшості класів, якщо значення повернення від [super init] дорівнює нулю, і ти перевіряєш його, як рекомендують стандартні практики, а потім повертається передчасно, якщо нуль, в основному ваш додаток все ще не працює належним чином. Якщо ви думаєте про це, не дивлячись на те, що якщо (саме! = Нуль) перевірка є, для правильної роботи вашого класу, 99,99% часу ви на самому справі робите потреба себе , щоб бути не дорівнює нулю. Тепер, припустимо, з будь-якої причини [super init] все- таки повернув нуль, в основному ваша перевірка проти нуля в основному передає долар абоненту вашого класу, де він, швидше за все, не вдасться, оскільки, природно, буде припускати, що дзвінок був успішний.

В основному, я отримую те, що 99,99% часу, якщо (self! = Nil) нічого не купує у плані більшої надійності, оскільки ви просто передаєте долар своєму провайдеру. Щоб реально впоратися з цим надійно, вам фактично потрібно було б встановити чеки у всій ієрархії викликів. І навіть тоді, єдине, що б вам придбало, - це те, що ваш додаток вийде з ладу трохи більш чисто / надійно. Але все-таки провалиться.

Якщо клас бібліотеки довільно вирішив повернути нуль в результаті [суперінітату], ви все одно в значній мірі, це більше свідчить про те, що автор бібліотечного класу допустив помилку в реалізації.

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

Але для коду рівня C я все одно перевіряю значення повернення malloc () проти покажчика NULL. Тоді як для Objective-C, поки не знайду доказів протилежного, я думаю, що я пропускаю перевірки if (self! = Nil). Чому невідповідність?

Тому що на рівнях С і малок в деяких випадках ви можете частково відновитися. Хоча я думаю, що в Objective-C, в 99,99% випадків, якщо [super init] поверне нуль, ви в основному знаходяться, навіть якщо ви намагаєтеся впоратися з цим. Ви також можете просто дозволити програмі вийти з ладу і вирішити наслідки.


6
Добре говорять. Я другий, що.
Roger CS Wernersson

3
+1 Я повністю згоден. Лише незначне зауваження: я не вірю, що модель є результатом часів, коли розподіл частіше виявляється невдалим. Виділення зазвичай вже робиться в той час, коли викликається init. Якщо Аллок не вдався, init навіть не зателефонував.
Микола Рухе

8

Це своєрідне резюме коментарів вище.

Скажімо, суперклас повертається nil. Що буде?

Якщо ви не дотримуєтесь конвенцій

Ваш код занепаде в середині вашого initметоду. (якщо тільки initнічого не означає)

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

Ваш код буде заборонено зламатись в якийсь момент пізніше, тому що ваш примірник там nil, де ви очікували чогось іншого. Або ваша програма буде вести себе несподівано, без збоїв. О Боже! Ви цього хочете? Не знаю...

Якщо ви дотримуєтесь умов, охоче дозволяючи підкласу повернути нуль

У кодовій документації (!) Повинно бути чітко зазначено: "повертається ... або нуль", а решта вашого коду потрібно підготувати для цього. Тепер це має сенс.


5
Цікавим моментом тут, на мою думку, є те, що варіант №1 явно кращий перед варіантом №2. Якщо є справді обставини, за яких, можливо, ви хочете, щоб ініт вашого підкласу повернув нуль, тоді слід віддати перевагу №3. Якщо єдина причина, що коли-небудь трапиться, - це помилка у вашому коді, тоді використовуйте №1. Використання опції №2 просто затримує ваш додаток вибухає до більш пізнього моменту, і тим самим зробити свою роботу, коли вам доведеться налагодити помилку набагато складніше. Це як мовчки ловити винятки та продовжувати без поводження з ними.
Марк Амерді

Або ви переходите до швидкого та просто користуєтеся додатковими
опціями

7

Як правило, якщо ваш клас походить безпосередньо з NSObject, вам цього не потрібно буде. Однак, це корисна звичка вступати, так як якщо ваш клас походить з інших класів, їх ініціалізатори можуть повернутися nil, і якщо так, ваш ініціалізатор може потім захопити це і поводити себе правильно.

І так, для запису я дотримуюсь найкращої практики і пишу її на всіх своїх заняттях, навіть тих, що походять безпосередньо з NSObject.


1
Зважаючи на це, чи було б корисною перевірити наявність нуля після ініціалізації змінної та перед викликом функцій на ній? наприкладFoo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software

Наслідування з боку NSObjectне гарантує, що це -initдає і вам NSObject, якщо рахувати екзотичні умови виконання, як-то старіші версії GNUstep в (які повертаються GSObject), так що незважаючи ні на що, чек і призначення.
Maxthon Chan

3

Ти маєш рацію, ти часто можеш просто писати [super init], але це не спрацює з підкласом просто нічого. Люди вважають за краще запам’ятати один стандартний рядок коду та використовувати його постійно, навіть коли це лише іноді необхідно, і, таким чином, ми отримуємо стандарт if (self = [super init]), який приймає як можливість повернення нуля, так і можливість об'єкта, відмінного від selfповернення в обліковому записі.


3

Поширена помилка - писати

self = [[super alloc] init];

який повертає екземпляр надкласу, який НЕ є тим, що потрібно у конструкторі / init підкласу. Ви повертаєте об’єкт, який не реагує на методи підкласу, що може заплутати, і створює заплутані помилки щодо невідповідності методам чи ідентифікаторам, які не знайдені тощо.

self = [super init]; 

потрібен, якщо в суперкласі є члени (змінні чи інші об'єкти), які слід ініціалізувати спочатку перед тим, як налаштувати членів підкласів. Інакше час виконання objc ініціалізує їх до 0 або до нуля . (на відміну від ANSI C, який часто виділяє шматки пам'яті, не очищаючи їх взагалі )

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


2

Це потрібно перевірити, чи працює інтіалазація, якщо оператор if повертає істину, якщо метод init не повернув нуль, тому його спосіб перевірити створення об'єкта працював правильно. Кілька причин, які я можу подумати, що ініт може зазнати невдачі, можливо, його переосмислений метод init, про який суперклас не знає, або щось подібне, я б не вважав, що це так часто. Але якщо це все-таки трапиться, то краще нічого не трапиться, щоб я зазнав аварії, тому її завжди перевіряють ...


так, але htey називаються разом, що станеться, якщо розподілити f ails?
Даніель

Я думаю, якщо аллока не вдасться, тоді init буде відправлений до нуля, а не до екземпляра будь-якого класу, на який ви викликаєте init. У цьому випадку нічого не станеться, і жоден код не буде виконаний, щоб навіть перевірити, повернув чи ні [super init] нуль.
Jasarien

2
Розподіл пам’яті не завжди виконується в + alloc. Розглянемо випадок класових класів; NSString не знає, який конкретний підклас використовувати, поки не буде викликано ініціалізатор.
bbum

1

В ОС X це не так вже ймовірно, що вона вийде -[NSObject init]з ладу через причини пам'яті. Те саме не можна сказати для iOS.

Крім того, це корисна практика для написання підкласингу класу, який може повернутися nilз будь-якої причини.


2
На обидва прошивці і Mac OS -[NSObject init]є дуже навряд чи вдасться з - за міркування пам'яті , як він не виділяє пам'ять.
Микола Рухе

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