Синглтоны - это классический паттерн, который есть почти в каждом iOS-проекте. Но в эпоху
Swift Concurrency они превращаются в мину замедленного действия, потому что это глобальное состояние, доступное из любого потока.
Обычный синглтон - это класс, а классы в
Swift по умолчанию не
thread-safe (не потокобезопасны). Когда вы обращаетесь к синглтону из разных потоков или акторов, возможны гонки данных и непредсказуемое поведение.
Решение проблемы:
Есть несколько стратегий:
- @MainActor:
@MainActor
class ThemeManager {
static let shared = ThemeManager()
private init() {}
var currentTheme = "light"
func switchToDarkTheme() {
currentTheme = "dark"
// Здесь обновляем UI
}
}
Как это работает:
- Гарантирует, что все обращения к синглтону будут на главном потоке.
- Компилятор сам проверяет изоляцию.
- Отлично подходит для синглтонов, которые влияют на интерфейс.
- @unchecked Sendable:
class CounterManager: @unchecked Sendable {
static let shared = NetworkManager()
private init() {}
// Используем NSLock для потокобезопасности
private let lock = NSLock()
private var count = 0
func increment() {
lock.lock()
count += 1
print("Count: \(count)")
lock.unlock()
}
}
// Использование:
CounterManager.shared.increment()
Это вариант для случаев, когда вы вручную обеспечиваете потокобезопасность через NSLock или подобные инструменты.
Вывод:
Для большинства проектов
@MainActor оказывается идеальным решением. Этот подход сочетает в себе простоту использования и надежную защиту от проблем с потоками.
Другие варианты вроде кастомных акторов или ручных блокировок часто создают больше сложностей, чем решают проблем. Они требуют постоянного использования await или тщательной ручной синхронизации, что лишает синглтоны их главного преимущества - простоты.