๐ŸŒŸ 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 Swiftโ€™s 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 .noneThatโ€™s 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. ๐Ÿค˜

Stay in the loop

Deep dives on Swift, SwiftUI, and building real apps โ€” from the code to the App Store. No fluff, no toy projects.