What Are the Most Common iOS App Crash Patterns and Fixes?

Type: Software Reference Confidence: 0.92 Sources: 7 Verified: 2026-02-23 Freshness: stable

TL;DR

Constraints

Quick Reference

# 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: FRONTBOARD8badf00d Move blocking work off main thread; launch <20s, resume <10s [src3]
6 Jetsam / out-of-memory (OOM) ~8% Termination Reason: Namespace JETSAMper-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]

Decision Tree

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

Step-by-Step Guide

1. Acquire and symbolicate the crash report

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.

2. Read the exception type and termination reason

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.

3. Enable diagnostic tools in Xcode

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.

4. Debug EXC_BAD_ACCESS with Zombie Objects

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.

5. Analyze memory issues with Instruments Allocations

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.

6. Set up crash reporting for production

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.

Code Examples

Swift: Safe optional handling patterns

// 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

Swift: Safe collection access extension

// 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

Objective-C: Zombie and unrecognized selector defense

// 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

Anti-Patterns

Wrong: Force-unwrapping optionals from external data

// 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

Correct: Conditional unwrapping with error handling

// 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
}

Wrong: Blocking the main thread on launch

// 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
}

Correct: Async loading with cached fallback

// 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
}

Wrong: Strong delegate reference causing zombie access

// BAD — strong reference creates retain cycle; crash risk [src5, src6]
class NetworkManager {
    var delegate: NetworkDelegate?  // STRONG reference
}

Correct: Weak delegate 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
    }
}

Wrong: Passing Core Data objects across threads

// 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
}

Correct: Use objectID and fetch on correct context

// 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
    }
}

Common Pitfalls

Diagnostic Commands

# === 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

Version History & Compatibility

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+

When to Use / When Not to Use

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

Important Caveats

Related Units