🌟 Static Formatters: The 5-Line Performance Win Every Swift Dev Should Know

🌟 Static Formatters: The 5-Line Performance Win Every Swift Dev Should Know

Quick tip before I finish moving boxes: stop creating new DateFormatters.

While unpacking boxes this week after a cross-country move, I ran into something that keeps showing up — not in my garage, but in Swift code. This post will be a brief one, but worth your read. DateFormatter() … everywhere. It’s one of those quiet performance hits (the kind that make QA say “why’s this screen lagging?”) that sneak into reducers and SwiftUI views like a stowaway in a carry-on.

I’ve seen this happen in flight-status updates, QA builds, even interviews. Someone says, “the timeline feels laggy,” and sure enough — new formatters are being born every render.


🧩 The Problem

Here’s a familiar SwiftUI pattern:

struct FlightView: View {
    let date: Date

    var body: some View {
        Text(DateFormatter().string(from: date)) // ❌
    }
}

At first glance, nothing looks wrong. But each body re-evaluation spawns a new DateFormatter, and those things aren’t light. They load locale info, calendars, and pattern metadata every single time.

Multiply that by a scrolling list of flights or timestamps, and you’ve got a jittery app.


⚙️ The Fix (The Five-Line Upgrade)

extension DateFormatter {
    static let flightTime: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d, h:mm a"
        return formatter
    }()
}```

Use it anywhere:
```swift
Text(DateFormatter.flightTime.string(from: date))Created once, reused forever, and thread-safe thanks to Swifts static initialization model.

🧬 In the Wild (TCA Example)

Here’s a before-and-after straight from a reducer I’ve seen more than once:

// ❌ Slow version
case .updateLastRefreshed(let date):
    let formatter = DateFormatter()
    formatter.dateFormat = "MMM d, h:mm a"
    state.lastRefreshed = formatter.string(from: date)
    return .nonevs.

// ✅ Clean version
case .updateLastRefreshed(let date):
    state.lastRefreshed = DateFormatter.flightTime.string(from: date)
    return .noneThats a small change that saves hundreds of unnecessary allocations in an app that updates timestamps often.

💸 Bonus: Beyond Dates

This trick also works with NumberFormatter, MeasurementFormatter, and ByteCountFormatter. They all benefit from static caching, especially when you’re formatting data repeatedly (like prices, weights, or storage sizes).


🦦 Why It Matters

Performance is rarely about big refactors. It’s about small, thoughtful habits that compound. Little stuff like this adds up — it’s how you make an app feel like it belongs on iOS. Static formatters are one of those habits — invisible to users, but felt everywhere.

🎯 Bonus: More Real-World iOS Survival Stories If you’re hungry for more tips, tricks, and battle-tested stories from the trenches of native mobile development, swing by: https://medium.com/@wesleymatlock.

Let’s keep building apps that rock — and if you’ve ever been burned by a rogue formatter, share your story. 🤘