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 真的很常见吗?如果是这样,在什么情况下,为什么?

4

9 回答 9

54

例如:

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

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

于 2009-08-17T13:30:18.717 回答
50

这个特殊的习语是标准的,因为它适用于所有情况。

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

[super init];

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

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

最重要的是,它是记录在案的正确使用模式,如果您不使用它,那么您做错了。

于 2009-08-17T17:04:59.660 回答
27

我认为,在大多数类中,如果 [super init] 的返回值为 nil 并且您按照标准做法的建议对其进行检查,然后如果为 nil,则过早返回,基本上您的应用程序仍然无法正常工作。如果您考虑一下,即使存在 if (self != nil) 检查,为了正确操作您的课程,99.99% 的时间您实际上确实需要 self 为非 nil。现在,假设,无论出于何种原因,[super init]确实返回了 nil,基本上你对 nil 的检查基本上是把责任推给你的类的调用者,无论如何它很可能会失败,因为它自然会假设调用是成功的。

基本上,我得到的是 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,那么即使您尝试处理它,您也基本上是完蛋了。您不妨让应用程序崩溃并处理后果。

于 2012-07-17T15:41:45.443 回答
8

这是对上述评论的一种总结。

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

如果你不遵守约定

init您的代码将在您的方法中间崩溃。(除非init没有做任何有意义的事情)

如果您遵循约定,不知道超类可能会返回 nil(大多数人最终都在这里)

你的代码很可能会在稍后的某个时候崩溃,因为你的实例是nil,你期望的地方会有所不同。或者您的程序会出现意外行为而不会崩溃。哦亲爱的!你想要这个吗?我不知道...

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

您的代码文档(!)应该清楚地说明:“返回......或零”,并且您的其余代码需要准备好处理这个问题。现在它是有道理的。

于 2010-07-18T12:01:50.767 回答
7

通常,如果您的类直接派生自NSObject,则不需要。但是,养成一个好习惯,就好像您的类派生自其他类一样,它们的初始化程序可能会返回nil,如果是这样,您的初始化程序就可以捕获它并正确运行。

是的,为了记录,我遵循最佳实践并将其写在我所有的课程中,即使是直接从NSObject.

于 2009-08-17T13:38:39.303 回答
3

你是对的,你通常可以只写[super init],但这不适用于任何东西的子类。人们更喜欢只记住一行标准代码并一直使用它,即使它只是有时需要,因此我们得到了标准if (self = [super init]),它既考虑了返回 nil 的可能性,也考虑了返回对象以外的对象的self可能性考虑到。

于 2009-08-17T16:52:35.830 回答
3

一个常见的错误是写

self = [[super alloc] init];

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

self = [super init]; 

如果超类有成员(变量或其他对象)在设置子类的成员之前首先初始化,则需要。否则 objc 运行时会将它们全部初始化为0nil。(不像 ANSI C,它经常分配内存块而不清除它们

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

于 2012-05-24T06:40:06.813 回答
2

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

于 2009-08-17T13:29:31.963 回答
1

在 OS X 中,-[NSObject init]由于内存原因而失败的可能性不大。iOS 不能这样说。

此外,在子类化可能nil因任何原因返回的类时,编写它是一种很好的做法。

于 2013-01-14T02:44:49.567 回答