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. ๐ค