A lazy singleton is initialized twice under load — why, and how do you fix it?
Under concurrent load, Manager's initializer runs more than once. Find why and fix the code so exactly one instance is ever created.
Constraints:
instance()may be called from many threads at the same time.- Keep it lazy — no instance is built until first use.
final class Manager {
static var shared: Manager?
static func instance() -> Manager {
if shared == nil { // two threads can both see nil here
shared = Manager()
}
return shared!
}
}
Find and fix the bug.
The check-then-set isn't atomic — two threads both create a Manager, so init runs twice. Fix with static let shared = Manager(), which Swift initializes exactly once, lazily and thread-safely. A lock or serial queue also works.
- ✗Believing a
static varcheck-then-set is atomic across threads - ✗Blaming the force-unwrap rather than the non-atomic guard
- ✗Adding
@MainActorinstead of usingstatic letfor one-time init
- →What guarantee does Swift give about when a
static letis initialized? - →How would a serial queue serialize the creation instead?
The if shared == nil check and the assignment aren't atomic, so two threads can both see nil and each create an instance. The Swift idiom is static let.
final class Manager {
static let shared = Manager() // lazy, exactly once, thread-safe
private init() {}
}
Swift guarantees a global/static let is initialized lazily and exactly once (backed by dispatch_once). The alternative is a lock or serial queue around the check, but static let is shorter and safe by default.