【问题标题】:In Objective-C why should I check if self = [super init] is not nil?在 Objective-C 中,我为什么要检查 self = [super init] 是否不为零?
【发布时间】:2010-11-20 06:02:33
【问题描述】:

我有一个关于在 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 真的很常见吗?如果是,在什么情况下,为什么?

【问题讨论】:

  • Wil Shipley 不久前发表了一篇与此相关的文章。 [self = [stupid init];](wilshipley.com/blog/2005/07/self-stupid-init.html) 也通读一下 cmets,一些好东西。
  • 您可以询问Wil ShipleyMike AshMatt Gallagher。无论哪种方式,这都是一个有争议的话题。但通常坚持使用 Apple 的惯用语是件好事……毕竟这是他们的框架。
  • Wil 似乎更愿意在 init 期间不要盲目地重新分配 self,因为他知道 [super init] 可能不会返回接收者。
  • 自从那篇文章最初发布以来,Wil 改变了他的想法。
  • 我之前看到过这个问题,现在又找到了。完美的。 +1

标签: objective-c null init


【解决方案1】:

例如:

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

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

【讨论】:

  • 是的,但这不在各自类的 init 方法中。 NSData 继承自 NSObject。 NSData 是否检查 [super init] 是否返回 nil?这就是我在这里要问的。抱歉,如果我不清楚...
  • 如果你要对这些类进行子类化,那么 [super init] 很有可能会返回 nil。并不是所有的类都是 NSObject 的直接子类。从来没有任何保证它不会返回零。这只是一种通常受到鼓励的防御性编码实践。
  • 我怀疑 NSObject init 在任何情况下都不能返回 nil。如果内存不足,alloc 将失败,但如果成功,我怀疑 init 可能会失败 - NSObject 除了 Class 之外甚至没有任何实例变量。在 GNUStep 中,它的实现只是“返回自我”,在 Mac 上反汇编看起来是一样的。当然,所有这些都是无关紧要的——只要遵循标准的成语,你就不必担心它是否可以。
  • 我并不执着于不遵循最佳实践。但是,我首先想知道为什么它们是最佳实践。就像被告知要从塔上跳下来一样。除非您知道原因,否则您不会继续这样做。是否有一个大而柔软的枕头可以放在底部,并获得巨额现金奖励?如果我知道我会跳。如果不是,我不会。我不想只是盲目地遵循一种做法而不知道我为什么要遵循它......
  • 如果 alloc 返回 nil,init 被发送到 nil,这将始终导致 nil,这最终以 self 为 nil。
【解决方案2】:

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

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

[super init];

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

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

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

【讨论】:

  • 根据可空性说明符,这仍然是真的吗?如果我的超类的初始化程序是非空的,那么检查额外的混乱是否真的值得? (虽然 NSObject 本身似乎没有任何 -init afaict...)
  • 当直接超类为NSObject 时,是否存在[super init] 返回nil 的情况?这不就是“万事俱备”的情况吗?
  • @DanRosenstark 如果NSObject 是直接超类,则不是。但是...即使您将NSObject 声明为直接超类,也可能在运行时进行了修改,使得NSObjectinit 实现不是实际调用的。
  • 非常感谢@bbum,这真的帮助我定位了一些错误修复。很好地排除了一些事情!
【解决方案3】:

我认为,在大多数类中,如果 [super init] 的返回值为 nil 并且您按照标准做法的建议检查它,然后如果为 nil,则过早返回,基本上您的应用程序仍然无法正常工作。 如果您考虑一下,即使存在 if (self != nil) 检查,为了您的类的正确操作,99.99% 的时间您实际上确实需要 self 为非 nil。 现在,假设,无论出于何种原因,[super init] did 返回 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,那么即使您尝试处理它,您也基本上是完蛋了。你还不如让应用崩溃并处理善后问题。

【讨论】:

  • 说得好。我同意这一点。
  • +1 我完全同意。只是一个小提示:我不认为这种模式是分配更经常失败的结果。分配通常在调用 init 时已经完成。如果 alloc 失败,甚至不会调用 init。
【解决方案4】:

这是对上述 cmets 的一种总结。

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

如果你不遵守约定

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

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

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

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

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

【讨论】:

  • 我认为这里有趣的一点是,选项#1明显优于选项#2。如果确实存在您可能希望子类的 init 返回 nil 的情况,则首选 #3。如果发生的唯一原因是代码中的错误,请使用#1。使用选项 #2 只是将您的应用程序爆炸延迟到稍后的时间点,从而使您在调试错误时的工作变得更加困难。这就像默默地捕获异常并继续而不处理它们。
  • 或者你切换到 swift 并且只使用可选项
【解决方案5】:

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

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

【讨论】:

  • 考虑到这一点,在初始化变量之后调用函数之前检查 nil 是否是个好习惯?例如Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
  • NSObject 继承并不能保证它的-init 也会给你NSObject,如果计算像旧版本的GNUstep 这样的奇异运行时(返回GSObject)所以无论如何,a检查和分配。
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    一个常见的错误是写

    self = [[super alloc] init];
    

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

    self = [super init]; 
    
    如果超类有成员(变量或其他对象)在设置子类的成员之前初始化首先,则需要

    。否则,objc 运行时会将它们全部初始化为 0nil。 (与 ANSI C 不同,后者通常分配内存块而不清除它们

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

    【讨论】:

      【解决方案8】:

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

      【讨论】:

      • 是的,但是htey被一起调用了,如果alloc失败了怎么办?
      • 我想如果 alloc 失败,那么 init 将被发送到 nil 而不是你调用 init 的任何类的实例。在这种情况下,什么都不会发生,甚至不会执行任何代码来测试 [super init] 是否返回 nil。
      • 内存分配并不总是在 +alloc 中完成。考虑类簇的情况;在调用初始化程序之前,NSString 不知道要使用哪个特定的子类。
      【解决方案9】:

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

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

      【讨论】:

      • 在 iOS 和 Mac OS 上,-[NSObject init]非常不太可能由于内存原因而失败,因为它不分配任何内存。
      • 我猜他的意思是 alloc,而不是 init :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-04
      • 1970-01-01
      • 1970-01-01
      • 2017-10-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多