No Object Instantiation In Loops
ID |
swift.no_objects_in_loops |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Efficiency |
Language |
Swift |
Tags |
efficiency, loops |
Description
Reports a heavy object being instantiated on every iteration of an
enclosing loop. Allocating a non-trivial type — JSONEncoder,
JSONDecoder, Regex, NSRegularExpression, XMLParser,
PropertyListSerialization, custom classes — inside a hot loop wastes
CPU on construction and forces ARC / GC to clean up short-lived
instances.
for s in items {
let d = JSONDecoder() // FLAW
out.append(try d.decode(T.self, from: s))
}
Rationale
Constructors that look cheap can do real work: parser state machines, backing buffer allocation, internal caches. When the same constructor runs N times in a tight loop, every iteration pays that fixed cost. The fix is almost always to allocate once and reuse, which is also clearer intent.
Remediation
Hoist the allocation out of the loop, or cache it on a static /
module-level constant when the cost is per-process:
// 1) hoist
let d = JSONDecoder()
for s in items {
out.append(try d.decode(T.self, from: s))
}
// 2) cache per process
private static let decoder = JSONDecoder()
When not to fire
The check is intentionally conservative and skips:
-
trivial value-type constructors (
Int(),String(),Array(),Dictionary(),Set(),Bool(), primitive numeric kinds) — those are cheap and idiomatic; -
Foundation formatter types — those are covered by the more focused
swift.cache_date_formattersrule; -
allocations nested inside a closure literal, function declaration, or initializer between the call and the enclosing loop — those run at most once per iteration (one-shot callback, lazy registration).