6.0 KiB
Debugging Tricks
- In daily development we often encapsulate a feature module and expose a method for external use. But many times callers don't pass parameters according to the contract. We use assertions to catch this. However, in production if assertions are used the app will crash. Xcode provides a small feature to address this.
NS_BLOCK_ASSERTIONS: prevents NSAssert from firing in Release builds; adding this single macro will filter out NSAssert.
How: In Build Settings search for Preprocessor Macros, then add NS_BLOCK_ASSERTIONS under the Release configuration.
Breakpoints
Categories
Breakpoints include Normal Breakpoint, Exception Breakpoint, OpenGL ES Error Breakpoint, Symbolic Breakpoint, Test Failure Breakpoint, and Watchpoint. Use different breakpoint types according to the scenario.
NSAssert and dispatch_once
NSAssert is very common in development, especially when building SDKs and libraries. Assertions help catch problems during development and enforce expected behavior. NSAssert essentially raises an exception; when an exception occurs it triggers the C function objc_exception_throw.
Callback information example:
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 TEst 0x0000000106edfeef -[AppDelegate application:didFinishLaunchingWithOptions:] + 287
5 UIKitCore 0x00007fff48089ad8 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 232
6 UIKitCore 0x00007fff4808b460 -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:] + 3980
7 UIKitCore 0x00007fff48090f05 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1226
8 UIKitCore 0x00007fff477c57a6 -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:] + 179
9 UIKitCore 0x00007fff4808d514 -[UIApplication _compellApplicationLaunchToCompleteUnconditionally] + 59
10 UIKitCore 0x00007fff4808d813 -[UIApplication _run] + 754
11 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621
12 TEst 0x0000000106ee0144 main + 116
13 libdyld.dylib 0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
You can clearly see when an assertion fails Xcode can precisely locate the line of code where NSAssert occurred—source is available. But in some scenarios exceptions cannot be traced to the real origin. For example, in large apps people often build Pods as static libraries to speed up builds; in those cases the exception cannot be precisely traced to the offending source line. If the assertion occurs inside a GCD block and the source context is missing, you also cannot pinpoint it.
Example output:
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 TEst 0x000000010f242e95 __57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke + 229
5 libdispatch.dylib 0x000000010f55fdd4 _dispatch_call_block_and_release + 12
6 libdispatch.dylib 0x000000010f560d48 _dispatch_client_callout + 8
7 libdispatch.dylib 0x000000010f56ede6 _dispatch_main_queue_callback_4CF + 1500
8 CoreFoundation 0x00007fff23bd4049 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
9 CoreFoundation 0x00007fff23bceca9 __CFRunLoopRun + 2329
10 CoreFoundation 0x00007fff23bce066 CFRunLoopRunSpecific + 438
11 GraphicsServices 0x00007fff384c0bb0 GSEventRunModal + 65
12 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621
13 TEst 0x000000010f243134 main + 116
14 libdyld.dylib 0x00007fff5227ec25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Add a Symbolic Breakpoint in Xcode for the symbol objc_exception_throw; then you can see the full stack in Xcode’s left-side navigator.
Check GCD’s _dispatch_client_callout to see if there’s anything unusual. You can find libdispatch source here: https://opensource.apple.com/tarballs/libdispatch/.
// object.m
#undef _dispatch_client_callout
void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
@try {
return f(ctxt);
}
@catch (...) {
objc_terminate();
}
}
You’ll find _dispatch_client_callout wraps the GCD block invocation in a try/catch that catches Objective-C exceptions and then calls objc_terminate, which breaks the call stack.
There’s a scenario where a crash shows up inside dispatch_once because code inside dispatch_once threw an Objective-C exception. Big companies often see this early on; later they add code around assertions specifically to record an owner or provide better diagnostics.


