Ленивый синглтон инициализируется дважды под нагрузкой — почему и как это исправить?
Под конкурентной нагрузкой инициализатор Manager выполняется больше одного раза. Найдите причину и исправьте код так, чтобы создавался ровно один экземпляр.
Ограничения:
instance()могут вызывать сразу из многих потоков.- Оставьте ленивость — экземпляр не создаётся до первого использования.
final class Manager {
static var shared: Manager?
static func instance() -> Manager {
if shared == nil { // два потока могут оба увидеть nil здесь
shared = Manager()
}
return shared!
}
}
Найдите и исправьте ошибку.
Проверка-и-присваивание не атомарны — два потока создают по Manager, поэтому init идёт дважды. Исправьте через static let shared = Manager() — Swift инициализирует его ровно раз, лениво и потокобезопасно. Блокировка или очередь тоже годятся.
- ✗Считают проверку-и-присваивание у
static varатомарной между потоками - ✗Винят force-unwrap, а не неатомарную проверку
- ✗Добавляют
@MainActorвместоstatic letдля одноразовой инициализации
- →Какую гарантию даёт Swift о моменте инициализации
static let? - →Как последовательная очередь сериализовала бы создание вместо этого?
Проверка if shared == nil и присваивание не атомарны, поэтому два потока успевают оба увидеть nil и создать по экземпляру. Идиома Swift — static let.
final class Manager {
static let shared = Manager() // ленивая, ровно один раз, потокобезопасно
private init() {}
}
Swift гарантирует, что глобальная/статическая let инициализируется лениво и ровно один раз (внутри — dispatch_once). Альтернатива — блокировка или последовательная очередь вокруг проверки, но static let короче и безопаснее по умолчанию.