My friend Ilja Iwas had a question: if a Mac app enables the ENABLE_NS_ASSERTIONS
build setting, why doesn't the app crash when it calls NSAssert
in the applicationDidFinishLaunching:
method of the NSApplicationDelegate
protocol? Good question! Curiously, the app does crash when it calls NSAssert
in the applicationWillFinishLaunching:
method, so we have a bit of a mystery.
The header file NSException.h
in the Foundation framework contains the definition of NSAssert
:
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self file:__assert_file__ \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
Looking at the Foundation framework disassembly ($ otool -tV /System/Library/Frameworks/Foundation.framework/Foundation), we can see that -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
calls +[NSException raise:format:arguments:]
with the exception name NSInternalInconsistencyException
. In turn, +[NSException raise:format:arguments:]
calls the objc_exception_throw
function of the Objective-C runtime. If we set a breakpoint on objc_exception_throw
, we can see that it does indeed get hit. So why doesn't the app crash?
Set another breakpoint on the objc_begin_catch
function. This breakpoint gets hit too! Now we have our answer. The exception is caught in the -[NSAppleEventManager dispatchRawAppleEvent:withRawReply:handlerRefCon:]
method of the AppKit framework. If we look at the backtrace, we can see that this method calls -[NSApplication(NSAppleEventHandling) _handleAEOpenEvent:]
, which posts an NSApplicationDidFinishLaunchingNotification
notification observed by the NSApplication
delegate. The key point is that when NSAppleEventManager
dispatches an Apple Event, it wraps the code in an Objective-C @try{}@catch{}
block. Thus, if applicationDidFinishLaunching:
throws an exception, the exception is harmlessly caught by NSAppleEventManager
instead of going uncaught and crashing the whole app.
It's important to note that even though NSAppleEventManager
catches exceptions, an exception still interrupts the flow of code in the applicationDidFinishLaunching:
method. It's still true that no code in the method following the NSAssert
will be executed. You might say, then, that NSAppleEventManager
renders NSAssert
"mostly harmless".
As a consequence of NSAppleEventManager
catching exceptions, NSAssert
will not crash an app in any method triggered by an Apple Event. There are many NSApplicationDelegate
methods triggered by Apple Events, such as applicationShouldHandleReopen:hasVisibleWindows:
to give one example. If you want to assure that your app crashes on assertions, you need to use the standard UNIX function assert
. Good old assert
, nothing beats that!