В Objective-C почему я должен проверить, не является ли self = [super init] нулевым?

У меня есть общий вопрос о написании методов 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 init может возвращать нуль? Если да, то в каком случае и почему?

Ответ 1

Например:

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

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

Ответ 2

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

В редких случаях будут случаи, когда...

[super init];

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

Скажем, суперкласс возвращает nil. Что произойдет?

Если вы не выполняете соглашения

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

Общей ошибкой является запись

self = [[super alloc] init];

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

self = [super init]; 

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

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

Ответ 8

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

Ответ 9

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

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