В 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
по любой причине.