我最近阅读了Apple的开发者技术支持大师Quinn“The Eskimo!”撰写的Apple MVCNetworking示例代码。该示例是非常好的学习经验,我认为这是iOS开发的最佳开发实践。 来自JVM语言的让我感到惊讶的是这样的非常频繁的断言:
syncDate = [NSDate date];
assert(syncDate != nil);
和此:
photosToRemove = [NSMutableSet setWithArray:knownPhotos];
assert(photosToRemove != nil);
和此:
photoIDToKnownPhotos = [NSMutableDictionary dictionary];
assert(photoIDToKnownPhotos != nil);
真的有必要吗?这种编码风格值得模仿吗?
答案 0 :(得分:4)
如果你习惯了Java,这可能看起来很奇怪。您希望对象创建消息在失败时抛出异常,而不是返回nil
。然而,虽然Objective-C on Mac OS X has support for exception handling;它是一个可选功能,可以使用编译器标志打开/关闭。编写标准库,以便可以在不启用异常处理的情况下使用它们:因此消息通常返回nil
来指示错误,有时还需要您传递指向NSError*
变量的指针。 (这是针对Mac开发的,我不确定你是否甚至可以为iOS启用异常处理支持,考虑到你也无法打开iOS的垃圾收集。)
文档“Objective-C编程语言”中的section "Handling Initialization Failure"解释了Objective-C程序员如何处理对象初始化/创建中的错误:即返回nil
。
[NSData dataWithContentsOfFile: path]
之类的内容肯定会返回nil
:该方法的文档明确说明了这一点。但我老实说不确定[NSMutableArray arrayWithCapacity: n]
之类的东西是否会返回nil
。当可能时,我能想到的唯一情况是应用程序内存不足。但在那种情况下,我希望通过尝试分配更多内存来中止应用程序。我没有检查过这个,在这种情况下它可能会返回nil
。虽然在Objective-C中你常常safely send messages to nil
,但这仍然会导致不良结果。例如,您的应用可能会尝试制作NSMutableArray
,而是获取nil
,然后愉快地继续向addObject:
发送nil
并将空文件写入磁盘而不是一个具有预期的数组元素。因此,在某些情况下,最好明确检查消息的结果是否为nil
。是否有必要在每个对象创建时进行,就像你引用的程序员正在做的那样,我不确定。比抱歉更安全吗?
编辑:我想补充一点,当检查时,对象创建成功有时可能是一个好主意,断言它可能不会是最好的主意。您还希望在应用程序的发行版中检查它,而不仅仅是在调试版本中。否则它有点无法检查它,因为你不希望应用程序最终用户,例如,因为[NSMutableArray arrayWithCapacity: n]
返回nil
并且应用程序继续发送消息而结束空文件nil
返回值。可以使用编译器标志从发布版本中删除断言(使用assert
或NSAssert
);默认情况下,Xcode在“Release”配置中似乎没有包含这些标志。但是如果您想使用这些标志来删除其他一些断言,那么您也将删除所有“对象创建成功”检查。
编辑:经过进一步反思,似乎比我第一次认为[NSMutableArray arrayWithCapacity: n]
返回nil
时更合理,而不是在没有足够内存可用时中止应用程序。基本C malloc
也不会中止,但在没有足够的内存可用时返回NULL
指针。但我还没有在alloc
和类似方法的Objective-C文档中发现任何明确的提及。
编辑:上面我说我不确定在每次创建对象时都需要检查nil
。但它不应该。这正是Objective-C允许向nil
发送消息的原因,nil
然后返回0
(或nil
或类似的东西,具体取决于消息定义):这样,{{1}可以通过您的代码传播,有点类似于异常,因此您不必须在可能返回它的每条消息上显式检查nil
。但最好在不希望它传播的位置检查它,例如在编写文件,与用户交互等时,或者在向nil
发送消息的结果中未定义(如documentation on sending messages to nil
中所述)。我倾向于说这就像异常传播和处理的“穷人”版本,虽然不是每个人都同意后者更好;但是nil
并没有告诉你发生错误的原因,你很容易忘记检查是否需要这样的检查。
答案 1 :(得分:3)
答案 2 :(得分:2)
烨。我认为这是一个好主意..一旦引入变量,它有助于过滤掉边缘情况(内存不足,输入变量为空/零)。虽然由于开销,我不确定对速度的影响!
答案 3 :(得分:2)
我想这是个人选择的问题。通常断言用于调试目的,以便在不满足条件时应用程序在断言点崩溃。您通常希望在应用程序版本上删除它们。
我个人懒得在你所展示的每个代码块周围放置断言。我认为这有点过于偏执了。在涉及某些不确定性的情况下,断言可能非常方便。
答案 4 :(得分:1)
我有also asked this on Apple DevForums。根据奎因“爱斯基摩人!” (MVCetworking样本的作者)它是matter of coding style and his personal preference:
我使用了很多断言,因为我讨厌调试。 (...)
请记住,我是在传统Mac OS中长大的,其中一个流氓指针可能会导致整个机器崩溃(类似于当前系统上的内核编程)。在那个世界中,尽快找到你的错误非常重要。很多断言帮助你这样做。
此外,即使在今天,我仍然花了很多时间处理网络程序。由于涉及异步,调试网络程序很难。断言对此有帮助,因为它们会在程序运行时不断检查程序的状态。
但是,我认为你对
+[NSDate date]
这样的东西有一个有效的观点。返回零的可能性很低。 断言纯粹来自习惯。但我认为这种习惯的成本(一些额外的打字,学会忽略断言)与收益相比很小。
从此我收集到断言每个对象创建成功并非绝对必要。
断言对于记录方法中的前提条件,在开发期间,作为其他维护者(包括未来的自我)的设计辅助非常有价值。 我个人更喜欢替代风格 - 使用TDD / BDD实践分离规范和实现。
由于Objective C的动态特性,断言可用于仔细检查方法参数的运行时类型:
assert([response isKindOfClass:[NSHTTPURLResponse class]]);
我确信断言有更好的用法。适度的一切......