在Objective-C中,为什么要检查self = [super init]是否不为零?


165

我有一个关于在Objective-C中编写init方法的一般问题。

我到处都看到(Apple的代码,书籍,开放源代码等),init方法应该在继续初始化之前检查self = [super init]是否不为零。

用于init方法的默认Apple模板是:

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

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

    return self;
}

为什么?

我的意思是init什么时候会返回nil?如果我在NSObject上调用init并返回nil,那么一定要搞砸了,对吧?在这种情况下,您甚至不编写程序...

类的init方法返回nil真的很常见吗?如果是这样,在什么情况下,为什么?


1
威尔·希普利(Wil Shipley)不久前发表了一篇与此相关的文章。[self = [stupid init];](wilshipley.com/blog/2005/07/self-stupid-init.html还要阅读注释,一些好东西。
Ryan Townshend

6
您可以问Wil ShipleyMike AshMatt Gallagher。无论哪种方式,这都是一个有争议的话题。但是通常最好还是坚持苹果的习惯用法……毕竟是它们的框架。
jbrennan

1
威尔(Wil)似乎在提出更多理由,不要在初始化期间盲目地重新分配自身,因为知道[超级初始化]可能不会返回接收方。
Jasarien

3
自从该帖子最初发表以来,Wil改变了想法。
bbum

5
我前一段时间已经看到了这个问题,然后又找到了。完善。+1
Dan Rosenstark'7年

Answers:


53

例如:

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

... 等等。(注意:如果文件不存在,NSData可能会引发异常)。在很多地方,当出现问题时,返回nil是预期的行为,因此,出于一致性的考虑,标准的做法是一直检查几乎所有的nil。


10
是的,但这不在相应类的init方法内部。NSData继承自NSObject。NSData是否检查[super init]是否返回nil?这就是我在这里要问的。抱歉,如果我不清楚的话……
Jasarien

9
如果要对这些类进行子类化,则[super init]很有可能会返回nil。并非所有类都是NSObject的直接子类。永远不能保证它不会返回nil。通常,这只是一种防御性的编码做法。
Chuck

7
我怀疑NSObject init在任何情况下都不会返回nil。如果内存不足,分配将失败,但是如果分配成功,我怀疑初始化可能会失败-NSObject甚至没有Class之外的任何实例变量。在GNUStep中,其实现只是“返回自我”,在Mac上的拆卸看起来也一样。当然,所有这些都是无关紧要的-只要遵循标准的习惯用法,您就不必担心它是否可以。
Peter N Lewis,

9
我并不是不遵循最佳实践的。但是,我想知道为什么它们首先是最佳实践。就像被告知跳下塔楼一样。除非您知道原因,否则您不仅会继续做下去。是否有一个大而柔软的枕头落在底部,并获得巨额现金奖励?如果我知道我会跳。如果没有,我不会。我不想盲目地跟随一种实践,而又不知道为什么要遵循它……
Jasarien

4
如果alloc返回nil,则将init发送到nil,这将始终导致nil,最终结果是self为nil。
T。

50

该特定用语是标准的,因为它在所有情况下都有效。

虽然不常见,但在某些情况下...

[super init];

...返回一个不同的实例,因此需要分配给self。

在某些情况下,它将返回nil,因此需要执行nil检查,以便您的代码不会尝试初始化不再存在的实例变量插槽。

底线是使用的已记录的正确模式,如果不使用它,那就错了。


3
根据可空性说明符,这仍然是正确的吗?如果我的超类的初始值设定项不是null,那么是否真的值得额外检查一下?(尽管NSObject本身似乎没有任何-init用处……)
natevw 2015年

有没有[super init]在直接超类为n时返回nil 的情况NSObject?这不是“一切都坏了”的情况吗?
Dan Rosenstark

1
@DanRosenstark不是NSObject直接超类。但是...即使您声明NSObject为直接超类,也可能在运行时修改了某些内容,使得NSObject的实现init实际上不是真正的调用。
bbum

1
非常感谢@bbum,这确实帮助我进行了一些错误修复。很高兴排除一些东西!
Dan Rosenstark

26

我认为,在大多数类中,如果[super init]的返回值是nil,然后按照标准做法的建议进行检查,然后过早返回(如果为nil),则基本上您的应用仍无法正常工作。如果您考虑一下,即使那里(self!= nil)检查存在,为了使您的班级正常运行,您实际上确实有99.99%的时间需要self不为nil。现在,假设,由于任何原因,[super init] 确实返回了nil,基本上,您对nil的检查基本上是将buck传递给您的类的调用者,无论如何它都可能失败,因为它自然会假设调用为成功。

基本上,我得到的是99.99%的时间,if(self!= nil)不会给您带来任何更大的健壮性,因为您只是将责任推给了调用者。为了真正能够可靠地处理此问题,实际上需要在整个调用层次结构中进行检查。即便如此,它唯一能买到的就是您的应用程序将更加干净/健壮地失败。但是它仍然会失败。

如果一个库类由于[super init]的结果而任意决定返回nil,那么无论如何您都会感到烦恼,这更多地表明该库类的编写者犯了一个实现错误。

我认为当应用程序在有限得多的内存中运行时,这更像是传统的编码建议。

但是对于C级代码,我通常仍将对照NULL指针检查malloc()的返回值。而对于Objective-C,直到我找到相反的证据,我认为我通常会跳过if(self!= nil)检查。为什么会有差异?

因为在C和malloc级别上,您实际上实际上可以部分恢复。我认为在Objective-C中,在99.99%的情况下,如果[super init]确实返回nil,即使您尝试处理它,也基本上会感到满意。您不妨让应用程序崩溃并处理后果。


6
说得好。我同意那个。
Roger CS Wernersson

3
+1我完全同意。只是一点点注意:我不认为这种模式是由于分配失败更多而导致的。通常在调用init时已经完成了分配。如果alloc失败,甚至不会调用init。
Nikolai Ruhe 2013年

8

这是上面评论的一种总结。

假设超类返回了nil。会发生什么?

如果您不遵守约定

您的代码将在init方法中间崩溃。(除非init无意义)

如果您遵循约定,则不知道超类可能返回nil(大多数人到此为止)

您的代码可能会在以后崩溃,因为您的实例是nil,您期望其中有所不同。否则您的程序将表现出意外而不会崩溃。噢亲爱的!你想要这个吗?我不知道...

如果您遵循约定,则愿意让您的子类返回nil

您的代码文档(!)应该清楚地声明:“ returns ... or nil”,并且需要准备其余的代码来处理此问题。现在有道理了。


5
我认为,这里有趣的一点是,选项1 显然比选项2 更可取。如果确实存在某些情况下,您可能希望子类的init返回nil,则首选#3。如果唯一可能发生的原因是代码中的错误,请使用#1。使用选项#2只是将应用程序的爆发延迟到以后的某个时间点,从而使您在调试错误时变得更加困难。就像静默地捕获异常并继续处理而不处理它们一样。
Mark Amery 2013年

或者您切换到快捷方式,而只使用可选选项
AndrewSB 2015年

7

通常,如果您的类直接从派生NSObject,则不需要。但是,这是一个好习惯,就像您的类是从其他类派生的一样,它们的初始化器可能会返回nil,如果这样,则您的初始化器可以捕获该异常并正确地执行操作。

是的,为了记录在案,我遵循最佳实践并将其写在我的所有班级上,即使是直接来自的班级也是如此NSObject


1
考虑到这一点,在初始化变量之后并在对它调用函数之前检查nil是否是个好习惯?例如Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software 2013年

如果将奇异的运行时(例如GNUstep的旧版本(返回))计数在内,那么无论如何都必须进行检查和赋值,否则从那里继承NSObject也不能保证-init为您提供。NSObjectGSObject
Maxthon Chan

3

没错,您通常可以只写[super init],但这对于仅包含任何子类的子类都无效。人们更愿意只记住一个标准的代码行,并用它所有的时间,即使它只是有时必要的,因此,我们得到的标准if (self = [super init]),这需要的零返回两者的可能性和对象比其他的可能性self正在返回考虑在内。


3

一个常见的错误是写

self = [[super alloc] init];

它返回超类的实例,这不是您在子类构造函数/ init中想要的。您将获得一个不响应子类方法的对象,这可能会造成混淆,并会产生关于不响应方法或未找到的标识符等令人困惑的错误。

self = [super init]; 

需要如果超类有成员(变量或其他对象)来初始化第一建立子类成员之前。否则,objc运行时会将它们全部初始化为0nil。(与ANSI C不同,后者通常分配内存块而根本不清除它们

是的,由于内存不足错误,缺少组件,资源获取失败等原因,基类初始化可能会失败,因此检查nil是明智的,并且只需不到几毫秒的时间。


2

这是为了检查初始化是否有效,如果init方法未返回nil,则if语句返回true,因此这是一种检查对象创建是否正确的方法。我能想到的init可能会失败的原因很少,也许是超类不知道的某种init重写的init方法或某种类似的东西,但我认为这并不常见。但是,如果确实发生了,那么最好不要发生我认为的崩溃,因此总是可以检查...


是的,但是htey一起被调用,如果分配失败,会发生什么?
丹尼尔(Daniel)2009年

我想如果alloc失败,那么init将被发送到nil,而不是您正在调用init的任何类的实例。在这种情况下,什么也不会发生,并且不会执行任何代码来测试[super init]是否返回nil。
Jasarien

2
内存分配并不总是在+ alloc中完成。考虑类集群的情况;在调用初始化程序之前,NSString不知道要使用哪个特定的子类。
bbum

1

在OS X中,-[NSObject init]由于内存原因它不太可能失败。iOS不能这么说。

同样,在将可能nil由于任何原因而返回的类子类化时,这也是编写的好习惯。


2
在这两个的iOS和Mac OS -[NSObject init]非常不可能的,因为它不分配任何内存由于无法记忆的原因。
Nikolai Ruhe 2013年

我猜他的意思是
分配
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.