Что такое Singleton? Что с ним не так?ios-56

Что такое Singleton?

Singleton (одиночка) — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.

Классическая реализация на Swift:

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

Где используется Singleton в iOS?

  1. Менеджеры ресурсов:

    • FileManager.default
    • URLSession.shared
    • UserDefaults.standard
  2. Сервисы приложения:

    • Аналитика (AnalyticsService.shared)
    • Кеширование (ImageCacheManager.shared)
    • Логирование (Logger.shared)
  3. Глобальные настройки:

    • Конфигурация приложения
    • Тема оформления
    • Текущая локаль

Преимущества Singleton

✅ Гарантирует единственный экземпляр
✅ Простота использования из любой точки программы
✅ Ленивая инициализация (в Swift по умолчанию)
✅ Удобен для ресурсоемких сервисов (например, кешей)

Проблемы Singleton

1. Нарушение принципов SOLID

  • SRP: Часто превращается в "божественный объект" со множеством ответственностей
  • DIP: Создает жесткие зависимости, которые сложно подменить в тестах

2. Проблемы с тестированием

func testSomeFeature() {
    let sut = SomeService()
    AppSettings.shared.currentTheme = .dark // Глобальное состояние

    sut.doSomething()

    // Тест зависит от глобального состояния, может быть хрупким
    XCTAssertEqual(sut.result, expectedResult)
}

3. Конкуренция за ресурсы

Без дополнительных мер не является потокобезопасным:

// Проблемный код:
static var shared = AppSettings() // Не потокобезопасная инициализация

// Решение:
static let shared: AppSettings = {
    // Любая дополнительная настройка
    return AppSettings()
}()

4. Скрытые зависимости

Классы, использующие синглтон, не объявляют свои зависимости явно:

class UserProfileViewController {
    func saveSettings() {
        // Скрытая зависимость - не видна в публичном API
        DatabaseManager.shared.saveUserData(...)
    }
}

5. Жизненный цикл

Синглтоны существуют всё время работы приложения, что может приводить к:

  • Утечкам памяти
  • Накоплению устаревших данных
  • Проблемам с очисткой ресурсов

Альтернативы Singleton

  1. Dependency Injection (предпочтительный способ):
class UserService {
    private let databaseManager: DatabaseManager

    init(databaseManager: DatabaseManager) {
        self.databaseManager = databaseManager
    }
}
  1. Статические методы (для чистых функций):
enum DateFormatterUtils {
    static func format(_ date: Date) -> String {
        let formatter = DateFormatter()
        return formatter.string(from: date)
    }
}
  1. Environment Objects (в SwiftUI):
@MainActor
class AppEnvironment: ObservableObject {
    @Published var currentUser: User?
}

// В иерархии View:
ContentView()
    .environmentObject(AppEnvironment())

Когда все же можно использовать Singleton?

  1. Для истинно глобальных ресурсов (например, URLSession.shared)
  2. Когда создание экземпляра действительно дорого
  3. Для логирования (хотя лучше использовать DI)
  4. В демонстрационных проектах для упрощения кода

Потокобезопасный Singleton

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-разработке предпочтение стоит отдавать внедрению зависимостей и другим способам управления состоянием. Используйте синглтон осознанно, только когда его преимущества перевешивают недостатки, и всегда документируйте причины его применения.

Как правило: если вы думаете "а не сделать ли это синглтоном?" — скорее всего, не стоит.