Home / Blog / How I hold a 0% crash rate across 12 App Store apps

How I hold a 0% crash rate across 12 App Store apps

Crash-rate discipline doesn't come from a tool, it comes from a code culture. The concrete techniques I use in production.

I check App Store Connect every morning. Across the 12 apps in my portfolio, the crash-free session rate has sat at 100% for a couple of years. That isn’t luck, it’s a deliberate discipline. Here are the techniques behind it.

Protect against nil, prefer throw

Unwrapping nil is still the most common crash in Swift. Force-unwrap (!) is a direct invitation to crash. I keep ! usage in the codebase close to zero.

The only place I use it is on @IBOutlet weak var x: UILabel! (Apple’s own convention). Everywhere else I handle the optional explicitly: guard let, if let, nil coalescing (??), or a sensible fallback.

Treat every network response as suspect

However solid the backend is, one day it will send the wrong response. Don’t assume “this field always comes back”.

Lock required fields down through Codable, and make everything else optional. If the backend sends something malformed, you get a DecodingError, not a crash. Log it and track it from there.

Don’t subscript arrays by index

array[5] crashes if the array has fewer than six elements. I write a safe subscript extension and use it in every project:

subscript(safe index: Int) -> Element? returns nil for an invalid index instead of crashing.

I add this extension on day one of every project. The code is a touch more verbose, but you’ll never see an “index out of range” crash again.

Don’t swallow throws in background tasks

Errors inside a Task { } often don’t reach the user, so developers are tempted to eat them. But a swallowed error becomes state inconsistency, and the state inconsistency is what eventually crashes.

Every catch has to either recover or log. An empty catch { } is unacceptable in the codebase. At minimum call recordError into Crashlytics or Sentry.

Catch main-thread violations early

UIKit and SwiftUI view operations have to happen on the main thread. Update a view from another thread and you usually don’t get an immediate crash, you get random misbehaviour, which is worse in production.

Swift 6’s concurrency checking catches these at compile time. On older projects I use the assert(Thread.isMainThread) pattern. In debug the assert crashes and you find the bug during development. In production the assert is a no-op, so the user doesn’t see it.

Never trust memory management

Swift’s ARC story is good, but closure retain cycles are still easy to create, especially in async closures inside view controllers. [weak self] is the default:

ViewModel callbacks, network completion handlers, timers, observation closures, all of them weak self. The only exception: the closure is guaranteed to finish before the view controller is deallocated.

I run the Leaks and Allocations instruments in Instruments before every feature release. Five minutes of work, a lot cheaper than shipping a memory leak.

Actually use TestFlight

Every build I push to TestFlight sits with testers for at least a week. “Works on my device” isn’t enough. Three device types (iPhone SE, iPhone 15, iPad), two iOS versions (current, current-1), different network conditions.

Once a build is out to testers, I watch the Crashes section in App Store Connect. A single crash in TestFlight and the release is paused.

Configure crash reporting properly

Firebase Crashlytics or Sentry, either is fine. What matters is configuring it properly:

  • Attach an anonymised user ID to every crash
  • Use breadcrumbs so the last five UI actions show up in the report
  • Add custom keys: which feature the user was in when it crashed
  • Don’t turn sampling on, capture every crash

Without this context a crash report tells you “where” but not “why”. A crash report without context is raw noise.

Takeaway

A 0% crash rate isn’t a metric, it’s a bundle of habits. A “could this crash?” question on every PR review. An Instruments pass before every deploy. Dashboard monitoring after every release.

It’s not expensive, just disciplined.

Have a project on this topic?

Leave a brief summary — I’ll get back to you within 24 hours.

Get in touch