Cache Date Formatters
ID |
swift.cache_date_formatters |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Efficiency |
Language |
Swift |
Tags |
efficiency, performance |
Description
Reports a Foundation formatter (DateFormatter,
ISO8601DateFormatter, DateComponentsFormatter, NumberFormatter
and friends) being constructed inside a loop body or a computed
property’s getter.
for row in rows {
let fmt = DateFormatter() // FLAW
fmt.dateFormat = "yyyy-MM-dd"
row.label = fmt.string(from: row.date)
}
struct View {
var formatted: String {
let fmt = ISO8601DateFormatter() // FLAW
return fmt.string(from: date)
}
}
Rationale
Foundation formatters are surprisingly expensive to instantiate:
constructing a DateFormatter parses calendar, locale and timezone
data and locks an internal cache, often taking a millisecond or more
even on modern devices. Inside a tight loop or a computed property
that is read on every layout pass, the cost stacks up quickly and
shows up in time-profiler traces.
Apple documents both DateFormatter and NumberFormatter as
thread-safe for read access once configured, so a single shared
instance is the idiomatic remedy.
Remediation
Hoist the formatter to a static or module-scope constant and configure it once:
private static let dayFormatter: DateFormatter = {
let f = DateFormatter()
f.dateFormat = "yyyy-MM-dd"
f.locale = Locale(identifier: "en_US_POSIX")
f.timeZone = TimeZone(identifier: "UTC")
return f
}()
for row in rows {
row.label = Self.dayFormatter.string(from: row.date)
}
If the formatter’s configuration depends on runtime state, cache the instance on the surrounding type and recompute only when the configuration changes.