Singleton (одиночка) — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
final class AppSettings {
// 1. Статическое свойство для хранения единственного экземпляра
static let shared = AppSettings()
// 2. Приватный инициализатор предотвращает создание других экземпляров
private init() {}
// 3. Свойства и методы класса
var currentTheme: Theme = .light
func toggleTheme() {
currentTheme = currentTheme == .light ? .dark : .light
}
}
// Использование:
AppSettings.shared.toggleTheme()
let currentTheme = AppSettings.shared.currentTheme
Менеджеры ресурсов:
FileManager.default
URLSession.shared
UserDefaults.standard
Сервисы приложения:
AnalyticsService.shared
)ImageCacheManager.shared
)Logger.shared
)Глобальные настройки:
✅ Гарантирует единственный экземпляр
✅ Простота использования из любой точки программы
✅ Ленивая инициализация (в Swift по умолчанию)
✅ Удобен для ресурсоемких сервисов (например, кешей)
func testSomeFeature() {
let sut = SomeService()
AppSettings.shared.currentTheme = .dark // Глобальное состояние
sut.doSomething()
// Тест зависит от глобального состояния, может быть хрупким
XCTAssertEqual(sut.result, expectedResult)
}
Без дополнительных мер не является потокобезопасным:
// Проблемный код:
static var shared = AppSettings() // Не потокобезопасная инициализация
// Решение:
static let shared: AppSettings = {
// Любая дополнительная настройка
return AppSettings()
}()
Классы, использующие синглтон, не объявляют свои зависимости явно:
class UserProfileViewController {
func saveSettings() {
// Скрытая зависимость - не видна в публичном API
DatabaseManager.shared.saveUserData(...)
}
}
Синглтоны существуют всё время работы приложения, что может приводить к:
class UserService {
private let databaseManager: DatabaseManager
init(databaseManager: DatabaseManager) {
self.databaseManager = databaseManager
}
}
enum DateFormatterUtils {
static func format(_ date: Date) -> String {
let formatter = DateFormatter()
return formatter.string(from: date)
}
}
@MainActor
class AppEnvironment: ObservableObject {
@Published var currentUser: User?
}
// В иерархии View:
ContentView()
.environmentObject(AppEnvironment())
URLSession.shared
)final class ThreadSafeSingleton {
static let shared = ThreadSafeSingleton()
private init() {}
private let queue = DispatchQueue(label: "com.example.singleton", attributes: .concurrent)
private var _value: Int = 0
var value: Int {
get {
queue.sync { _value }
}
set {
queue.async(flags: .barrier) { [weak self] in
self?._value = newValue
}
}
}
}
Singleton — это мощный инструмент, который стал жертвой собственной популярности. Хотя он решает конкретные проблемы, его чрезмерное использование приводит к:
В современной iOS-разработке предпочтение стоит отдавать внедрению зависимостей и другим способам управления состоянием. Используйте синглтон осознанно, только когда его преимущества перевешивают недостатки, и всегда документируйте причины его применения.
Как правило: если вы думаете "а не сделать ли это синглтоном?" — скорее всего, не стоит.