В Objective-C, почему я должен проверить, если self = [super 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 и получил nil обратно, то что-то должно быть действительно испорчено, верно? И в этом случае вы могли бы даже не написать программу ...

Действительно ли так часто, что метод init класса может возвращать ноль? Если да, то в каком случае и почему?

17.08.2009 13:25:11
Уил Шипли недавно опубликовал статью, связанную с этим. [self = [глупый init];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) Прочитайте также комментарии, некоторые полезные вещи.
Ryan Townshend 17.08.2009 13:30:32
Вы могли бы спросить Уила Шипли или Майка Эша или Мэтта Галлахера . В любом случае, это что-то из обсуждаемой темы. Но обычно хорошо придерживаться идиом Apple: в конце концов, это их рамки.
jbrennan 17.08.2009 13:30:37
Похоже, Уил делал больше аргументов для того, чтобы не слепо переназначать себя во время init, зная, что [super init] может не вернуть получателя.
Jasarien 17.08.2009 15:22:04
Уил изменил свои мысли с тех пор, как этот пост был изначально сделан.
bbum 17.08.2009 18:35:46
Я видел этот вопрос некоторое время назад и просто нашел его снова. Отлично. +1
Dan Rosenstark 9.07.2010 01:53:28
9 ОТВЕТОВ
РЕШЕНИЕ

Например:

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

... и так далее. (Примечание: NSData может выдать исключение, если файл не существует). Существует довольно много областей, в которых возвращение nil - это ожидаемое поведение при возникновении проблемы, и из-за этого является стандартной практикой постоянно проверять наличие nil для обеспечения согласованности.

53
17.08.2009 13:30:18
Да, но это не ВНУТРИ метода init соответствующего класса. NSData наследуется от NSObject. NSData проверяет, возвращает ли [super init] ноль? Это то, что я спрашиваю здесь. Извините, если я не ясно ...
Jasarien 17.08.2009 13:33:54
Если бы вы создали подклассы этих классов, вполне вероятно, что [super init] вернет nil. Не все классы являются прямыми подклассами NSObject. Там нет никакой гарантии, что он не вернет ноль. Это просто защитная практика кодирования, которая обычно поощряется.
Chuck 17.08.2009 13:38:23
Я сомневаюсь, что NSObject init может вернуть nil в любом случае, когда-либо. Если у вас недостаточно памяти, alloc не будет работать, но если это удастся, я сомневаюсь, что init может потерпеть неудачу - NSObject даже не имеет переменных экземпляра, кроме Class. В GNUStep он реализован как просто «вернуть себя», а разборка на Mac выглядит одинаково. Все это, конечно, не имеет значения - просто следуйте стандартным идиомам, и вам не придется беспокоиться о том, может это или нет.
Peter N Lewis 17.08.2009 14:38:13
Я не склонен к тому, чтобы не следовать лучшим практикам. Однако я хотел бы знать, почему они являются наилучшей практикой в ​​первую очередь. Это как если бы мне сказали прыгнуть с башни. Вы не просто идете вперед и делаете это, если не знаете почему. Есть ли большая мягкая подушка для приземления внизу с огромным денежным вознаграждением? Если бы я знал, что прыгнул бы. Если нет, я бы не стал. Я не хочу просто слепо следовать за практикой, не зная, почему я следую за ней ...
Jasarien 17.08.2009 15:12:04
Если alloc возвращает nil, init отправляется в nil, что всегда приводит к nil, в результате чего self равно nil.
T . 17.08.2009 18:32:47

Это делается для того, чтобы проверить, что инициализация сработала, оператор if возвращает true, если метод init не вернул nil, поэтому это способ проверить, что создание объекта работало правильно. Несколько причин, по которым я могу думать о том, что init может потерпеть неудачу, может быть, это переопределенный метод init, о котором суперкласс не знает, или что-то в этом роде, хотя я не думаю, что это так часто встречается. Но если это случится, лучше ничего не случиться, если я получу аварию, так что ее всегда проверяют ...

2
17.08.2009 18:48:27
да, но они вызываются вместе, что будет, если выделить файлы?
Daniel 17.08.2009 13:50:36
Я полагаю, что если не удается выполнить alloc, тогда init будет отправляться на nil, а не на экземпляр какого-либо класса, для которого вы вызываете init. В этом случае ничего не произойдет, и никакой код не будет выполнен, чтобы даже проверить, вернул ли [super init] nil.
Jasarien 17.08.2009 15:08:20
Выделение памяти не всегда выполняется в + alloc. Рассмотрим случай кластеров классов; строка NSString не знает, какой конкретный подкласс использовать, пока не будет вызван инициализатор.
bbum 17.08.2009 18:36:34

Как правило, если ваш класс является производным от NSObjectвас, вам это не нужно. Тем не менее, это хорошая привычка, как если бы ваш класс был производным от других классов, их инициализаторы могут возвращаться nil, и если это так, ваш инициализатор может затем захватить это и вести себя правильно.

И да, для протокола: я следую лучшей практике и пишу ее на всех своих уроках, даже на тех, которые происходят непосредственно от них NSObject.

7
17.08.2009 13:38:39
Имея это в виду, будет ли хорошей практикой проверять nil после инициализации переменной и перед вызовом для нее функций? напр.Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
svrs 4.09.2013 19:14:35
Наследование NSObjectне гарантирует его -initдает вам NSObjectтоже, если подсчет экзотических , как время автономной работы более старых версий GNUstep в (который возвращается GSObject) , поэтому не важно , что, чек и Присвоить.
Maxthon Chan 23.10.2013 09:27:05

Вы правы, вы часто можете просто написать [super init], но это не сработает для подкласса всего, что угодно. Люди предпочитают просто запомнить одну стандартную строку кода и использовать ее постоянно, даже когда это только иногда необходимо, и, таким образом, мы получаем стандарт if (self = [super init]), который использует как возможность возврата nil, так и возможность объекта, отличного от selfвозврата в учетную запись.

3
17.08.2009 16:52:35

Эта идиома является стандартной, потому что она работает во всех случаях.

Хотя необычно, будут случаи, когда ...

[super init];

... возвращает другой экземпляр, поэтому требует присваивания себе.

И будут случаи, когда он вернет nil, поэтому потребуется проверка nil, чтобы ваш код не пытался инициализировать слот переменной экземпляра, который больше не существует.

Суть в том, что это задокументированный правильный шаблон для использования, и, если вы его не используете, вы делаете это неправильно.

50
17.08.2009 17:04:59
Это все еще верно в свете спецификаторов обнуляемости? Если инициализатор моего суперкласса не нулевой, то стоит ли проверять дополнительный беспорядок? (Хотя сам -initNSObject, кажется, не имеет ничего для своего афикта ...)
natevw 17.12.2015 18:14:53

Это своего рода краткое изложение комментариев выше.

Допустим, суперкласс возвращается nil. Что будет дальше?

Если вы не следуете конвенциям

Ваш код потерпит крах в середине вашего initметода. (если initне делает ничего значимого)

Если вы будете следовать соглашениям, не зная, что суперкласс может вернуть ноль (большинство людей в конечном итоге здесь)

Ваш код, вероятно, потерпит крах в какой-то момент позже, потому что ваш экземпляр находится там nil, где вы ожидали чего-то другого. Или ваша программа будет работать неожиданно без сбоев. О, Боже! Вы хотите это? Я не знаю...

Если вы будете следовать соглашениям, добровольно разрешив вашему подклассу вернуть ноль

Ваша документация по коду (!) Должна четко указывать: «возвращает ... или ноль», а остальная часть вашего кода должна быть подготовлена ​​для обработки этого. Теперь это имеет смысл.

8
18.07.2010 12:01:50
Интересно отметить, что вариант № 1 явно предпочтительнее варианта № 2. Если есть действительно обстоятельства, при которых вы можете захотеть, чтобы init вашего подкласса возвратил nil, тогда предпочтение должно отдаваться # 3. Если единственная причина, которая когда-либо случится, это ошибка в вашем коде, тогда используйте # 1. Использование опции № 2 просто задерживает взрыв вашего приложения до более позднего момента времени, и, таким образом, делает вашу работу намного сложнее, когда вы приходите к отладке ошибки. Это все равно что молча ловить исключения и продолжать без их обработки.
Mark Amery 5.07.2013 15:20:18
Или вы переключаетесь на swift и просто используете дополнительные функции
AndrewSB 14.05.2015 22:59:56

Распространенная ошибка - написать

self = [[super alloc] init];

который возвращает экземпляр суперкласса, который НЕ является тем, что вы хотите в конструкторе / init подкласса. Вы возвращаете объект, который не отвечает на методы подкласса, что может сбивать с толку, и генерировать ошибочные ошибки, связанные с невозможностью ответить на методы или идентификаторы, которые не найдены, и т. Д.

self = [super init]; 

нужно , если супер класс имеет член (переменные или другие объекты) , чтобы инициализировать первый перед установкой членов подклассов. В противном случае среда выполнения objc инициализирует их все в 0 или в ноль . (в отличие от ANSI C, который часто выделяет куски памяти, не очищая их вообще )

И да, инициализация базового класса может произойти из-за ошибок нехватки памяти, отсутствующих компонентов, сбоев получения ресурсов и т. Д., Поэтому проверка на ноль является разумной и занимает менее нескольких миллисекунд.

3
24.05.2012 06:40:06

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

По сути, я нахожусь в том, что в 99,99% случаев if (self! = Nil) ничего не покупает с точки зрения большей надежности, поскольку вы просто перекладываете деньги на своего призывателя. Чтобы действительно иметь возможность справиться с этим, вам действительно нужно было бы проверять всю иерархию вызовов. И даже в этом случае единственное, что он может купить, - это то, что ваше приложение не сможет работать более чисто / надежно. Но все равно не получится.

Если библиотечный класс произвольно решил вернуть nil в результате [super init], вы в любом случае в значительной степени ошеломлены, и это скорее указывает на то, что автор библиотечного класса допустил ошибку реализации.

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

Но для кода уровня C я все равно обычно проверял бы возвращаемое значение malloc () по нулевому указателю. Принимая во внимание, что для Objective-C до тех пор, пока я не найду доказательства обратного, я думаю, что обычно я пропускаю проверки if (self! = Nil). Почему расхождение?

Потому что на уровнях C и malloc в некоторых случаях вы можете частично восстановиться. В то время как я думаю, что в Objective-C, в 99,99% случаев, если [super init] действительно возвращает nil, вы в сущности офигены, даже если пытаетесь с этим справиться. С таким же успехом вы можете просто позволить приложению рухнуть и разобраться с последствиями.

26
17.07.2012 15:41:45
Хорошо сказано. Я второй это.
Roger C S Wernersson 11.12.2012 08:17:09
+1 Я полностью согласен. Небольшое замечание: я не верю, что шаблон является результатом времен, когда распределение чаще всего не удавалось. Распределение обычно уже выполняется во время вызова init. Если не удалось выделить alloc, init даже не был бы вызван.
Nikolai Ruhe 15.12.2013 16:21:01

В OS X это не так вероятно для -[NSObject init]сбоя по причинам памяти. То же самое нельзя сказать о iOS.

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

1
14.01.2013 02:44:49
На оба прошивке и Mac OS -[NSObject init]является очень вряд ли удастся из - за соображения памяти , как он не выделяет память.
Nikolai Ruhe 15.12.2013 16:33:45
Я предполагаю, что он имел в виду alloc, а не init :)
Thomas Tempelmann 24.03.2014 12:06:30