我有一个关于用Objective-C编写init方法的一般问题。
在继续初始化之前,init方法应该检查self=[super init]是否不为零。
init方法的默认Apple模板是:
1 2 3 4 5 6 7 8 9 10 11
| - (id) init
{
self = [super init];
if (self != nil)
{
// your code here
}
return self;
} |
为什么?
我的意思是init什么时候能把零还回来?如果我在nsobject上打电话给init,却又得到了nil,那么一定有什么事情真的搞砸了,对吧?在这种情况下,你最好不要写程序…
类的init方法可能返回nil真的很常见吗?如果是这样,在什么情况下,为什么?
- Wil Shipley发表了一篇相关文章。[自= [愚蠢init ];](WiSrpLy.com /博客/ 2005 / 07 /愚蠢的init .html)阅读评论,以及一些好东西。
- 你可以问威尔希普利,迈克阿什或马特加拉赫。不管怎样,这都是一个有争议的话题。但通常坚持苹果的成语是好的…毕竟,这是他们的框架。
- 似乎wil在init期间没有盲目地重新分配self,因为他知道[super init]可能不会返回接收者。
- 自从那篇文章发表以来,威尔改变了主意。
- +对于那些很棒的链接。
- 我刚才看到这个问题,又发现了。很完美。+ 1
- +好极了,链接很好。伙计,我得回去写代码,别再看这些东西了。但首先让我注意到,没有一位作者建议不使用if。但@jasarien已经说过了,差不多…
- 这看起来很奇怪。[super init]听起来像init的super类。为什么要把它赋给子类?为什么(self=[self init])不是?有人能帮忙吗?谢谢杰米
- bbum说的完全正确。
例如:
1 2 3
| [[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"]; |
…等等。(注意:如果文件不存在,nsdata可能会引发异常)。在很多领域,当出现问题时,返回零是预期的行为,因此,为了一致性,经常检查零是标准做法。
- 是的,但这不是在各自的类init方法中。NSATS从NSObject继承。NSATATE检查是否[超init ]返回NIL?这就是我要问的。对不起,如果我不清楚…
- 如果您要对这些类进行子类化,那么[super init]很有可能返回nil。并非所有类都是nsObject的直接子类。没有任何保证它不会返回零。这只是一种普遍鼓励的防御性编码实践。
- @Jasarien:可以在你调用的super的init方法中完成。当然,nsObject可能不会失败初始化,但是如果您是MyDataAndImageClass的子类,那么它很可能会在其init中失败。
- 我同意查克和朱布伦南的观点。另外,当我创建一个新的类时,需要5秒钟来编写if (self = [super init]),但是要想知道我的超类是否可以返回nil还需要相当多的时间。当然,当你的记忆力耗尽的时候。
- 但是在我的子类中,我不会调用-in i t,我会调用initWithImage:或initWithContentsOfFile:在这种情况下,我希望使用nil。但是当子类化一个nsObject时,nil是否应该出现呢?
- 是的-如果内存不足。这很罕见,但也有可能发生。另外,为了一行代码,你为什么这么拼命不做最好的事情?防御代码是很好的代码。
- 我怀疑nsobject init在任何情况下都不能返回nil。如果内存不足,alloc将失败,但如果成功,我怀疑init可能会失败-nsObject除了类之外甚至没有任何实例变量。在gnustep中,它被实现为"返回自我",在mac上分解看起来是一样的。当然,所有这些都是无关紧要的——只要遵循标准的成语,你就不必担心它是否可以。
- 我并不是刻意不遵循最佳实践。然而,我想知道为什么它们首先是最好的实践。就像被要求从塔上跳下来。除非你知道原因,否则你不可能就这么做。有没有一个大的,软枕头着陆在底部,以巨额现金奖励?如果我知道我会跳。如果不是的话,我不会的。我不想只是盲目地遵循一个实践而不知道我为什么要遵循它…
- 这也不是说我的代码没有遵循这一点。我检查我所有的子类,我只是想知道我为什么要这样做;)
- @Jasarien:原因是通常不能保证任何任意的init方法在某个时刻都不会返回nil,所以这是一个很好的实践。我个人认为这有点偏执,但这就是原因。
- 如果alloc返回nil,init将被发送到nil,这将始终导致nil,这最终导致self为nil。
- @查克,我不明白的是,当你构造一个类的实例时,除非你知道由于某种原因它可能是空的,否则你不会检查它是否是空的。所以不管怎样,你的代码还是会爆炸的。那么,在不检查对象是否为空的代码上下文中,这些防弹构造函数是否有意义呢?似乎是一个愚蠢的惯例,简洁是有价值的。
- 另一点我在这里没有提到,你应该检查一下nil是否从[super init]返回,因为这个超类将来可能会改变。在这种情况下,只要做这个简单的检查就可以节省一些时间。另外,我相信Xcode现在将这个签入模板代码中,当您创建一个新的objective-c子类时,您将得到这个签入模板代码,这样您的in it s中就不再有任何实际的理由不包含它了。
- 这也是斯威夫特所关心的吗?
这个特殊的成语是标准的,因为它在所有情况下都有效。
虽然不常见,但也有一些情况…
…返回另一个实例,因此需要将分配给self。
在某些情况下,它将返回nil,因此需要进行nil检查,这样代码就不会尝试初始化不再存在的实例变量槽。
归根结底,它是一个文档化的正确模式,如果您不使用它,那么您就错了。
- 根据空性说明符,这仍然是真的吗?如果我的超类的初始值设定项不是空的,那么它真的值得检查额外的混乱吗?(尽管nsobject本身似乎没有用于其-initafaict…)
我认为,在大多数类中,如果[super in it]的返回值为零,并且您按照标准实践的建议检查它,然后过早返回(如果为零),那么基本上您的应用程序仍然无法正常工作。如果你想的话,即使那样如果(自己!=nil)检查是否存在,为了正确操作您的班级,99.99%的时间您确实需要自己非nil。现在,假设不管出于什么原因,[super init]返回了nil,基本上您对nil的检查基本上是将buck传递给您的类的调用方,在那里它可能无论如何都失败,因为它自然会假定调用是成功的。
基本上,我得到的是99.99%的时间,如果(自我!=nil)不会为您购买更强大的健壮性方面的任何东西,因为您只是将buck传递给您的调用程序。为了真正能够可靠地处理这个问题,实际上需要对整个调用层次结构进行检查。即便如此,它也会给你带来的唯一好处就是你的应用程序会更干净/更强大地失效。但它仍然会失败。
如果一个库类由于一个[super init]而擅自决定返回nil,那么无论如何,您都是被错误地对待了,这更说明库类的作者在实现上犯了错误。
我认为这更像是一个遗留的编码建议,当应用程序在有限的内存中运行时。
但是对于C级代码,我仍然会针对空指针检查malloc()的返回值。然而,对于目标C,在我找到相反的证据之前,我认为我通常会跳过if(self!=零)检查。为什么存在差异?
因为,在C和malloc级别,在某些情况下实际上可以部分恢复。而我认为在Objective-C中,99.99%的情况下,如果[Super Init]返回零,那么即使你试图处理它,你也基本上是F**ed。你也可以让应用崩溃,然后处理后果。
- 说得很好。我再说一遍。
- +我完全同意。只是一个小注意:我不相信这种模式是分配的时间往往失败的结果。分配通常是在调用IT的时候完成的。如果OLLC失败,init甚至不会被调用。
这是对上述评论的总结。
假设超类返回nil。会发生什么?
如果你不遵守惯例
您的代码将在init方法的中间崩溃。(除非init没有任何意义)
如果你遵循惯例,不知道超类可能返回零(大多数人都会在这里结束)
您的代码很可能在以后的某个时候崩溃,因为您的实例是nil,在这里您期望的是不同的东西。或者你的程序会表现得出乎意料而不会崩溃。哦,天哪!你想要这个吗?我不知道…
如果您遵循这些约定,愿意让您的子类返回nil
您的代码文档!!)应该清楚地说:"退货……或者是nil",剩下的代码需要准备好处理这个问题。现在这是有道理的。
- 我认为,这里有趣的一点是,方案1显然比方案2更可取。如果确实存在这样的情况,您可能希望子类的init返回nil,那么3是首选。如果发生这种情况的唯一原因是因为代码中的错误,那么使用1。使用选项2只是将应用程序爆炸延迟到稍后的时间点,从而使您在调试错误时的工作变得更加困难。这就像是默默地捕捉异常并继续而不处理它们。
- 或者你换成斯威夫特,只使用期权
通常,如果类直接从NSObject派生,则不需要这样做。但是,有一个好习惯,就像你的类是从其他类派生的,它们的初始值设定项可能返回nil,如果是这样,你的初始值设定项就可以捕获它并正确地执行操作。
是的,作为记录,我遵循最佳实践并将其写在我所有的类上,甚至直接从NSObject派生的类上。
- 考虑到这一点,在初始化变量之后以及在对变量调用函数之前检查nil是否是一个好的实践?如Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}。
- 从NSObject继承并不保证它的-init也给了你NSObject,如果计算像gnustep的旧版本(返回GSObject的)那样的异域运行时间,那么不管怎样,检查和分配。
一个常见的错误是写作
1
| self = [[super alloc] init]; |
它返回超类的一个实例,这不是您在子类构造函数/init中想要的。您将返回一个不响应子类方法的对象,这可能会造成混淆,并生成关于不响应方法或未找到标识符等的混淆错误。
在设置子类成员之前,如果父类具有要首先初始化的成员(变量或其他对象),则需要使用。否则,objc运行时将它们全部初始化为0或nil。(与ANSIC不同,后者通常在不清除内存块的情况下分配内存块)
是的,由于内存不足错误、缺少组件、资源获取失败等原因,基类初始化可能会失败。因此检查nil是明智的,并且花费的时间不到几毫秒。
你是对的,你可以经常只写[super init],但这对任何子类都不起作用。人们宁愿只记住一行标准代码并一直使用它,即使有时只是必要的,这样我们就得到了标准的if (self = [super init]),它既考虑了返回nil的可能性,也考虑了返回self以外的对象的可能性。
这是为了检查inAZAZATION是否工作,如果init方法没有返回零,if语句返回true,因此它是检查对象创建正确的方法。我能想到init的一些原因可能会失败,可能是超级类不知道的一个init init方法或类似的东西,但我不认为这是常见的。但如果它真的发生了,那就更好了。
- 内存分配是在alloc中完成的,而不是init。
- 是的,但是他们被召集在一起,如果分配失败会发生什么?
- 我只是猜测一下,我可能错了。
- 我想如果alloc失败,那么init将被发送到nil,而不是您调用init的任何类的实例。在这种情况下,不会发生任何事情,也不会执行任何代码来测试[super init]是否返回nil。
- 是的,我可能错了。
- 内存分配并不总是在+OLLC中完成。考虑类簇的情况;在调用初始化器之前,NScript不知道要使用哪个特定子类。
在OSX中,由于内存原因,-[NSObject init]失败的可能性不大。iOS也不能这么说。
此外,在对可能出于任何原因返回nil的类进行子类化时,编写该类也是一种良好的实践。
- 在iOS和Mac OS上,-[NSObject init]不太可能由于内存原因而失败,因为它不分配任何内存。
- 我猜他是说alloc,不是init:)