刚学习Objective-C那会儿,还不太了解这个世界的惯用法,所以有些地方使用了C/C++的方式,虽然后来做过一定的修改, 但是项目中还是遗留了一些无关紧要的C/C++代码。比如对断言的运用。
assert(some != nil);
问题
使用assert倒是没遇到啥问题,不过有一次在查阅测试同学提交上来的crashlog, 发现竟然崩在了assert调用上。
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x3aaf61fc __pthread_kill + 8
1 libsystem_pthread.dylib 0x3ab5fa2e pthread_kill + 54
2 libsystem_c.dylib 0x3aaa6ff8 abort + 72
3 libsystem_c.dylib 0x3aa86d22 __assert_rtn + 178
4 test 0x000f8b36 -[AppDelegate application:didFinishLaunchingWithOptions:]
......
15 CoreFoundation 0x300c014a __CFRunLoopRun + 1394
16 CoreFoundation 0x3002ac22 CFRunLoopRunSpecific + 518
17 CoreFoundation 0x3002aa06 CFRunLoopRunInMode + 102
18 UIKit 0x328d2dd4 -[UIApplication _run] + 756
19 UIKit 0x328ce044 UIApplicationMain + 1132
20 test 0x000f8bde main (main.m:16)
21 libdyld.dylib 0x3aa3fab4 start + 0
还真是稀奇,这到底是怎么回事呢?
缘由
首先,assert不是应该只在NDEBUG没有定义的时候有效么?难道Release默认没有设置这个宏?
通过Xcode日志很容易就发现Release下果然没有设置NDEBUG,验证了前面的假设。

可是为什么Release下默认没有设置这个宏呢?我记得NDEBUG 是语言标准的一部分啊?
查阅文档, 结果如下。

也就是说, NDEBUG确实是语言的标准,但是标准只定义了它是怎么影响assert的, 并没有定义编译器应该在什么情况下定义NDEBUG,所以Xcode在Relase模式下没有定义也是合乎标准的。
继续挖掘
问题找到原因了,可是我还是不死心,那如果Xcode是这样对待assert的, 那么自家的NSCAssert呢?
在Foundation/NSException.h中,NSCAssert大致是这样的定义的:
#if !defined(NS_BLOCK_ASSERTIONS)
#define NSCAssert(condition, desc, ...)
do {
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS
if (!(condition)) {
[[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__]
file:[NSString stringWithUTF8String:__FILE__]
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__];
}
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS
} while(0)
#else
#define NSCAssert(condition, desc, ...) do {} while (0)
#endif
与assert由NDEBUG来控制类似, NSCAssert是由NS_BLOCK_ASSERTIONS控制的。
那么Release下的NSCAssert会不会也会触发程序崩溃呢?
通过查阅Xcode在Release模式下的构建日志,发现答案是不是的。

总结
我只能说Xcode这里做的略不厚道,既然你给自家的NSCAssert定义了开关,那么也应该关照到assert。
作为开发人员,我们有两个处理办法。
- 不用
assert, 完全改成NSCAssert(注意不要使用NSAssert,详见这里)。但是要同时注意你使用的第三方代码。 - 在工程设置
Build Settings -> Preprocessor Macros -> Release中添加NDEBUG=1, 如下图。
