В чем плюсы и минусы синглтона
Что проверяют
Видите ли вы в синглтоне не только удобство, но и цену: скрытые зависимости, глобальное состояние, проблемы с тестами и потоками. Следом почти всегда спрашивают, как тестировать код, который зовёт .shared, и что меняет строгая проверка конкурентности в Swift 6.
Как отвечать
В Swift синглтон обычно выглядит так: static let shared = Service() и private init(). Инициализация static let ленивая и потокобезопасная, язык гарантирует это сам.
Плюсы:
- один экземпляр на ресурс: там, где он действительно один, как
UIApplication.shared; - простота доступа: не нужно протаскивать объект через цепочку экранов;
- общий дорогой ресурс: кэш изображений, конфигурация сетевой сессии.
Минусы:
- скрытые зависимости: из сигнатуры не видно, что класс ходит в сеть или в базу;
- тестируемость: реализацию не подменить, а состояние перетекает между тестами;
- потоки: потокобезопасна только инициализация, изменяемое состояние внутри требует
actor,@MainActorили последовательной очереди; - время жизни: объект живёт до конца процесса. Типичный баг: после выхода из аккаунта синглтон держит данные прошлого пользователя.
Многие системные объекты это общие экземпляры, а не строгие синглтоны: рядом с URLSession.shared, FileManager.default, UserDefaults.standard можно создать свои.
Практичный компромисс: закрыть синглтон протоколом и передавать через инициализатор со значением по умолчанию, init(api: APIClient = NetworkService.shared). В проде ничего не меняется, в тесте подставляется мок.
В режиме Swift 6 компилятор выдаёт ошибку на static let shared с не-Sendable типом: тип придётся сделать Sendable, превратить в actor или изолировать на @MainActor.
Где спотыкаются
Говорят, что синглтон потокобезопасен, раз он один. Гонка данных внутри него никуда не девается.
Вызывают .shared глубоко в бизнес-логике. Потом такой код не покрыть тестами без переписывания.
Превращают синглтон в свалку: AppManager с сетью, кэшем и настройками сразу. Это уже god object, а не паттерн.