🚨 Swift 6 и Singleton: что изменилось?

С выходом Swift 6 включается строгая проверка конкурентности (strict concurrency checking). Это значит, что:

✅ Раньше: ты мог использовать синглтон из любого потока — компилятор лишь предупреждал

❌ Теперь: компилятор выдаёт ошибку, если твой синглтон не потокобезопасен

🧱 Почему это важно?

Синглтоны обычно создаются так:

final class MyManager { static let shared = MyManager() // ⚠️ Потенциально небезопасно private init() {} func doSomething() { /* ... */ } }

Этот shared может быть вызван из разных потоков, и без защиты от одновременного доступа это вызывает data races.

✅ Как сделать синглтон безопасным в Swift 6

1. actor — идеальный способ для полной изоляции

actor Logger { static let shared = Logger() private init() {} func log(_ message: String) { print("[LOG]: \(message)") } }

🔐 Все вызовы методов внутри actor автоматически сериализуются — то есть выполняются по одному, без гонок данных.

2. @MainActor — когда синглтон работает только на main thread

@MainActor final class UIStateManager { static let shared = UIStateManager() private init() {} func update() { // безопасно только с main thread } }

🎯 Используется для ViewModel, NavigationCoordinator, ThemeManager — всего, что связано с UI.

3. Sendable + ручная защита или let-поля

final class Config: Sendable { static let shared = Config() private init() {} let isFeatureEnabled = true // ✅ безопасно, потому что immutable }

🧊 Хорошо для конфигураций, флагов, настроек и других объектов, которые не изменяются.

⚠ Плохой пример (так больше нельзя в Swift 6)

final class UnsafeManager { static let shared = UnsafeManager() private init() {} var count = 0 func increment() { count += 1 // ❌ ошибка: доступ к `count` из разных потоков } }

🔴 В Swift 6 это вызовет ошибку компиляции, если вызвать shared.increment() из разных actor-контекстов.

💬 Какой способ выбрать?

  • Если работа связана с UI, например ViewModel, темы, навигация — используй @MainActor. Это гарантирует, что всё выполняется на главном потоке.
  • Если у тебя логгеры, сетевые сервисы или менеджеры, которые могут вызываться из разных потоков — используй actor. Это обеспечит безопасную конкурентную изоляцию.
  • Если твой синглтон — это просто конфигурация, флаги или другие неизменяемые данные, используй Sendable и сделай все свойства let.
  • Если у тебя старый код, и ты пока не хочешь/не можешь его переделать, можно временно использовать @unchecked Sendable. Но это не рекомендуется — лучше всё же адаптировать под новый подход.

🚀 Заключение

Swift 6 не убивает Singleton — он делает его безопасным. Если у тебя в проекте есть shared, проверь, защищён ли он. Это легко исправить — и компилятор поможет тебе.

Начать дискуссию