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

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