刚学习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
, 如下图。