xcrun atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x100012345
atos or Xcode Organizer for symbolication. [src1,
src4]| # | Crash Pattern | Likelihood | Exception Type / Signature | Fix |
|---|---|---|---|---|
| 1 | Force-unwrap nil optional | ~20% | EXC_BREAKPOINT (SIGTRAP) — Unexpectedly found nil while unwrapping
|
Replace ! with guard let, if let, or
?? [src2,
src6]
|
| 2 | Unrecognized selector sent | ~15% | EXC_CRASH (SIGABRT) —
-[NSObject doesNotRecognizeSelector:] |
Check IBOutlet connections; verify protocol conformance; use
responds(to:) [src1,
src6]
|
| 3 | Array/collection index out of bounds | ~12% | EXC_CRASH (SIGABRT) — Fatal error: Index out of range |
Bounds-check with .indices.contains(i) or use .first,
.last [src1,
src6]
|
| 4 | EXC_BAD_ACCESS (zombie/dangling pointer) | ~10% | EXC_BAD_ACCESS (SIGSEGV) — KERN_INVALID_ADDRESS at
objc_msgSend |
Enable Zombie Objects in Debug; use weak instead of unowned [src2,
src5] |
| 5 | Watchdog termination (0x8badf00d) | ~10% | Termination Reason: FRONTBOARD — 8badf00d |
Move blocking work off main thread; launch <20s, resume <10s [src3] |
| 6 | Jetsam / out-of-memory (OOM) | ~8% | Termination Reason: Namespace JETSAM — per-process-limit |
Reduce memory; downsample images; use autoreleasepool in loops [src7]
|
| 7 | Failed type cast (as!) |
~7% | EXC_BREAKPOINT (SIGTRAP) — Could not cast value of type |
Use as? conditional cast instead of as! [src2,
src6]
|
| 8 | Main thread UI violation | ~5% | EXC_BREAKPOINT — Main Thread Checker: UI API called on background thread |
Wrap UI updates in DispatchQueue.main.async {} or use @MainActor
[src1,
src6]
|
| 9 | Stack overflow (deep recursion) | ~3% | EXC_BAD_ACCESS (SIGSEGV) — crash near thread stack guard page |
Add recursion base case; convert to iteration [src2] |
| 10 | Missing weak delegate / notification observer | ~3% | EXC_BAD_ACCESS — crash in objc_msgSend to deallocated delegate
|
Declare delegates as weak var; remove observers in deinit [src5, src6]
|
| 11 | KVO observer not removed | ~2% | EXC_CRASH (SIGABRT) — key value observers were still registered
|
Remove KVO observers in deinit or use NSKeyValueObservation token
[src1]
|
| 12 | Core Data concurrency violation | ~2% | EXC_BAD_INSTRUCTION or EXC_BAD_ACCESS inside Core Data stack |
Use perform {} / performAndWait {}; never pass
NSManagedObject across threads [src1,
src4] |
| 13 | Swift concurrency data race (Swift 6) | ~2% | EXC_BREAKPOINT — runtime exclusivity or Sendable violation |
Enable strict concurrency checking; use actors for shared state [src2] |
START — iOS app crashed
├── Do you have a symbolicated crash report?
│ ├── YES → Read Exception Type field
│ │ ├── EXC_BREAKPOINT (SIGTRAP)?
│ │ │ ├── "Unexpectedly found nil" → Cause #1: Force-unwrap nil
│ │ │ ├── "Could not cast value" → Cause #7: Failed type cast
│ │ │ ├── "UI API called on background thread" → Cause #8: Main thread violation
│ │ │ └── Exclusivity violation → Cause #13: Swift concurrency data race
│ │ ├── EXC_CRASH (SIGABRT)?
│ │ │ ├── "doesNotRecognizeSelector" → Cause #2: Unrecognized selector
│ │ │ ├── "Index out of range" → Cause #3: Array out-of-bounds
│ │ │ └── "key value observers still registered" → Cause #11: KVO not removed
│ │ ├── EXC_BAD_ACCESS (SIGSEGV)?
│ │ │ ├── Address is 0x0 or very low → NULL pointer dereference
│ │ │ ├── objc_msgSend at top of stack → Cause #4: Zombie/dangling pointer
│ │ │ └── Address near stack guard page → Cause #9: Stack overflow
│ │ └── No exception type (Termination Reason)?
│ │ ├── 0x8badf00d / FRONTBOARD → Cause #5: Watchdog timeout
│ │ └── Namespace JETSAM → Cause #6: Out-of-memory
│ └── NO → Symbolicate first
│ ├── Have dSYM? → xcrun atos or Xcode Organizer
│ └── No dSYM? → Check Xcode archive; re-archive if possible
│
└── Crash only on specific iOS version?
├── YES → Check API availability; use #available / @available guards
└── NO → Proceed with exception type analysis above
Crash reports are useless without symbolication. Xcode Organizer auto-symbolicates if you archived the build with dSYMs. [src1, src4]
# Manual symbolication with atos
xcrun atos -arch arm64 \
-o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-l 0x100a40000 \
0x100a56789
# Output: -[ViewController viewDidLoad] (in MyApp) (ViewController.swift:42)
# Verify dSYM matches binary UUID
dwarfdump --uuid MyApp.app.dSYM
dwarfdump --uuid MyApp.app/MyApp
# Both should print the same UUID
Verify: atos output shows function names and line numbers, not hex addresses.
The exception type tells you the crash category. [src2]
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a4c5e4f8
Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5
# Backtrace of crashed thread:
0 libswiftCore.dylib _swift_runtime_on_report + 28
1 libswiftCore.dylib _swift_stdlib_reportFatalError + 24
2 MyApp ViewController.viewDidLoad() + 120
Verify: You can identify the exception type and the faulting frame in your code.
Turn on runtime sanitizers and checkers for the Debug scheme. [src1, src5]
Xcode > Product > Scheme > Edit Scheme > Run > Diagnostics:
[x] Address Sanitizer (ASan) — use-after-free, buffer overflow
[x] Thread Sanitizer (TSan) — data races (mutually exclusive with ASan)
[x] Main Thread Checker — UI calls from background threads
[x] Zombie Objects — messages to deallocated objects
[x] Undefined Behavior Sanitizer — UB in C/C++/ObjC code
Only enable ONE of ASan or TSan at a time.
Verify: Run the app. If ASan or TSan detects an issue, Xcode pauses at the violation.
When the crash is at objc_msgSend with an invalid address, enable Zombie Objects. [src5]
// Console output when zombie is accessed:
// *** -[MyViewController respondsToSelector:]: message sent to deallocated instance 0x600003a08000
// In lldb, inspect the zombie:
// (lldb) po 0x600003a08000
// Output: <_NSZombie_MyViewController: 0x600003a08000>
Verify: The console identifies the class of the deallocated object.
For jetsam/OOM crashes, profile memory usage to find the allocation spike. [src7]
Xcode > Product > Profile (Cmd+I) > Allocations
1. Record session while reproducing high-memory scenario
2. Look at "All Heap & Anonymous VM" for total memory
3. Sort by "Persistent Bytes" for largest allocations
4. Use "Mark Generation" between actions to see per-action growth
5. iOS apps jetsammed at ~1.5-2GB on modern devices (varies)
Verify: Persistent Bytes total stays under device memory limit.
Configure MetricKit (built-in, iOS 14+) for crash diagnostics without third-party SDKs. [src4, src6]
import MetricKit
class MetricSubscriber: NSObject, MXMetricManagerSubscriber {
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
if let crashDiagnostics = payload.crashDiagnostics {
for crash in crashDiagnostics {
print(crash.callStackTree.jsonRepresentation())
}
}
}
}
}
// Register in AppDelegate
let subscriber = MetricSubscriber()
MXMetricManager.shared.add(subscriber)
Verify: After a crash, didReceive(_:) is called within 24 hours with the
diagnostic payload.
// Input: Optional values from network/DB/user input
// Output: Crash-safe code that handles nil gracefully
// PATTERN 1: guard let (early return)
func processUser(data: [String: Any]?) {
guard let data = data,
let name = data["name"] as? String,
let age = data["age"] as? Int else {
print("Invalid user data")
return
}
print("\(name), age \(age)")
}
// PATTERN 2: if let (conditional execution)
if let url = URL(string: urlString) {
UIApplication.shared.open(url)
}
// PATTERN 3: nil coalescing (default value)
let displayName = user.name ?? "Anonymous"
// PATTERN 4: Optional chaining
let count = user.orders?.filter { $0.isPaid }?.count ?? 0
// Input: Array with unknown bounds
// Output: Crash-safe subscript access
extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
// Usage — never crashes
let items = ["a", "b", "c"]
let item = items[safe: 5] // nil instead of crash
let first = items.first // Optional — safe
let last = items.last // Optional — safe
// Input: Delegate callbacks and selector invocations
// Output: Crash-safe patterns for dynamic dispatch
// PATTERN 1: Weak delegate (prevents EXC_BAD_ACCESS)
@interface MyClass ()
@property (nonatomic, weak) id<MyDelegate> delegate;
@end
// PATTERN 2: Check selector before calling
if ([self.delegate respondsToSelector:@selector(didFinishLoading:)]) {
[self.delegate didFinishLoading:self];
}
// PATTERN 3: Safe KVO with NSKeyValueObservation token
_observation = [model observe:@selector(status)
options:NSKeyValueObservingOptionNew
handler:^(MyModel *obj, NSKeyValueChange *change) {
NSLog(@"Status: %@", change.newValue);
}];
// Auto-removes on dealloc — no manual removeObserver
// BAD — network response can be nil; force-unwrap crashes [src1, src6]
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let name = json["name"] as! String
let age = json["age"] as! Int
// GOOD — graceful handling of missing or mistyped data [src1, src6]
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let name = json["name"] as? String,
let age = json["age"] as? Int else {
throw DataError.invalidFormat
}
// BAD — blocks main thread; watchdog kills app after 20s [src3]
func application(_ application: UIApplication,
didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let data = try! Data(contentsOf: URL(string: "https://api.example.com/config")!)
self.config = try! JSONDecoder().decode(Config.self, from: data)
return true
}
// GOOD — non-blocking; loads config async with cached fallback [src3]
func application(_ application: UIApplication,
didFinishLaunchingWithOptions opts: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.config = Config.loadCached() ?? Config.defaults
Task {
if let fresh = try? await fetchConfig() {
self.config = fresh
fresh.saveToCache()
}
}
return true
}
// BAD — strong reference creates retain cycle; crash risk [src5, src6]
class NetworkManager {
var delegate: NetworkDelegate? // STRONG reference
}
// GOOD — weak breaks retain cycle; nil check prevents crash [src5, src6]
class NetworkManager {
weak var delegate: NetworkDelegate?
func notifyCompletion() {
delegate?.didComplete() // Optional chaining — safe
}
}
// BAD — NSManagedObject is not thread-safe [src1, src4]
let user = viewContext.fetch(request).first!
DispatchQueue.global().async {
print(user.name) // EXC_BAD_ACCESS on background thread
}
// GOOD — objectID is thread-safe; fetch on correct context [src1, src4]
let userID = user.objectID
DispatchQueue.global().async {
let bgContext = persistentContainer.newBackgroundContext()
bgContext.perform {
let bgUser = bgContext.object(with: userID) as! User
print(bgUser.name) // Safe
}
}
xcrun atos. [src1,
src4]Termination Reason:
FRONTBOARD, not Exception Type. Fix: focus on main thread blocking, not memory
bugs. [src3]
unowned used where weak should be: unowned
crashes immediately on deallocation; weak safely becomes nil. Fix: use weak by
default. [src5, src6]
MXMetricManagerSubscriber. Fix: register in
didFinishLaunchingWithOptions. [src1]
#available for new APIs: Calling unavailable APIs causes
EXC_BAD_ACCESS or Symbol not found. Fix: guard with
if #available(iOS 17, *). [src1,
src6]
# === Symbolicate a crash address ===
xcrun atos -arch arm64 \
-o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
-l 0x100a40000 \
0x100a56789
# === Verify dSYM matches binary ===
dwarfdump --uuid MyApp.app.dSYM
dwarfdump --uuid MyApp.app/MyApp
# === lldb crash debugging commands ===
# (lldb) bt # Full backtrace
# (lldb) bt all # All threads
# (lldb) frame variable # Local variables
# (lldb) po self # Print object description
# (lldb) memory read 0x600003a08000 # Read memory at address
# (lldb) register read # CPU register state
# (lldb) image lookup -a 0x100a56789 # Symbol for address
# === Xcode diagnostic runtime flags ===
# Address Sanitizer: use-after-free, buffer overflow
# Thread Sanitizer: data races (mutually exclusive with ASan)
# Main Thread Checker: UI calls from background threads
# Zombie Objects: messages to deallocated objects
# === Instruments profiling ===
# Xcode > Product > Profile (Cmd+I)
# - Allocations: memory growth, jetsam debugging
# - Leaks: retain cycle detection
# - Zombies: use-after-free with allocation history
# - Time Profiler: main thread blocking / watchdog
| iOS / Xcode | Status | Key Crash Debugging Features | Notes |
|---|---|---|---|
| iOS 18 / Xcode 16 | Current | Swift 6 strict concurrency; enhanced crash reports with actor isolation | Swift 6 catches data races at compile time [src2] |
| iOS 17 / Xcode 15 | Supported | Interactive crash logs in Organizer; improved MetricKit payloads | MetricKit now includes hang diagnostics |
| iOS 16 / Xcode 14 | Supported | Swift concurrency runtime checks; async backtraces in lldb | — |
| iOS 15 / Xcode 13 | Maintenance | MetricKit crash diagnostics; extended launch stall reporting | First version with MXCrashDiagnostic |
| iOS 14 / Xcode 12 | Deprecated | MetricKit basic metrics; App Clips crash separation | MetricKit v1 (metrics only, no crash diagnostics) |
| iOS 13 / Xcode 11 | EOL | SwiftUI initial release; Combine framework crashes | Many SwiftUI crashes fixed in iOS 14+ |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Your iOS app crashes with a crash report | App hangs but does not crash | Instruments Time Profiler + MXAppExitMetric |
| Crash report shows EXC_BAD_ACCESS, EXC_CRASH, or EXC_BREAKPOINT | Crash in React Native / Flutter JS/Dart layer | Framework-specific debugger (Flipper, DevTools) first |
| Users report crashes from TestFlight or App Store | App runs out of memory silently | Xcode Instruments Allocations + Jetsam event reports [src7] |
| Debugging Objective-C zombie / unrecognized selector | Debugging a C/C++ library linked into the iOS app | C/C++ Segfault Debugging |
| Identifying the most common crash in production | Profiling app startup or rendering performance | Instruments Time Profiler or os_signpost |
EXC_BREAKPOINT when strict concurrency is enabled. This is
intentional. [src2]