← Назад к вопросам

Как реализовать единственный экземпляр без использования Singleton?

2.0 Middle🔥 131 комментариев
#Архитектура и паттерны#Язык Swift

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Альтернативы Singleton для единственного экземпляра

Хотя Singleton является классическим решением, он имеет ряд недостатков (глобальное состояние, сложность тестирования, нарушение SRP), поэтому в современной iOS-разработке часто используют альтернативные подходы. Вот основные способы реализации единственного экземпляра без паттерна Singleton.

1. Dependency Injection (Внедрение зависимостей)

Самый популярный и предпочтительный подход — передача зависимостей через инициализаторы или свойства. Создаётся один экземпляр на уровне композиции корня приложения и передаётся туда, где нужен.

class NetworkService {
    func fetchData() { /* ... */ }
}

class ViewModel {
    private let networkService: NetworkService
    
    init(networkService: NetworkService) {
        self.networkService = networkService
    }
    
    func loadData() {
        networkService.fetchData()
    }
}

// В точке композиции (AppDelegate, SceneDelegate, корневом ViewController)
let sharedNetworkService = NetworkService()
let viewModel = ViewModel(networkService: sharedNetworkService)

2. Фабричные методы с контролем инстансов

Использование фабрики, которая отслеживает созданные экземпляры и возвращает существующий при повторном запросе.

class DatabaseManager {
    private static var sharedInstance: DatabaseManager?
    
    private init() {
        // Приватный инициализатор
    }
    
    static func shared() -> DatabaseManager {
        if sharedInstance == nil {
            sharedInstance = DatabaseManager()
        }
        return sharedInstance!
    }
    
    // Альтернатива: сброс для тестирования
    static func resetForTesting() {
        sharedInstance = nil
    }
}

3. Использование Swift Global Constants

В Swift глобальные константы инициализируются лениво и потокобезопасно, что позволяет создать shared instance без классического Singleton.

final class ConfigurationManager {
    private init() {}
    
    func configure() { /* ... */ }
}

let sharedConfigurationManager: ConfigurationManager = {
    let instance = ConfigurationManager()
    instance.configure()
    return instance
}()

4. Environment Objects (в SwiftUI)

В SwiftUI можно использовать @EnvironmentObject для распространения данных по всей иерархии представлений.

class UserSession: ObservableObject {
    @Published var isLoggedIn = false
    @Published var user: User?
}

// В корневом View
ContentView()
    .environmentObject(UserSession())

5. Service Locator Pattern

Паттерн "Локатор служб", который регистрирует и предоставляет сервисы по запросу.

protocol ServiceLocating {
    static func resolve<T>() -> T
}

final class ServiceLocator: ServiceLocating {
    private static var services: [String: Any] = [:]
    
    static func register<T>(_ service: T) {
        let key = String(describing: T.self)
        services[key] = service
    }
    
    static func resolve<T>() -> T {
        let key = String(describing: T.self)
        guard let service = services[key] as? T else {
            fatalError("Service \(key) not registered")
        }
        return service
    }
}

// Регистрация и использование
ServiceLocator.register(NetworkService())
let networkService: NetworkService = ServiceLocator.resolve()

6. Использование Protocol-Oriented подхода

Определение протокола с требованием shared instance, но оставляя реализацию на усмотрение конкретного типа.

protocol SharedInstanceProtocol {
    static var shared: Self { get }
}

class AnalyticsTracker: SharedInstanceProtocol {
    private init() {}
    
    static let shared = AnalyticsTracker()
    
    func track(event: String) { /* ... */ }
}

7. Модули/Бандлы с контролируемым доступом

Организация кода в отдельные модули (frameworks) с контролем видимости инициализаторов.

// Внутри framework
public class SecureStorage {
    internal init() {} // Доступен только внутри модуля
    
    public static let shared = SecureStorage()
}

Сравнение подходов

  • Dependency Injection — наиболее гибкий и тестируемый подход, соответствует принципам чистой архитектуры
  • Environment Objects — идеален для SwiftUI приложений с реактивным потоком данных
  • Service Locator — полезен в крупных приложениях со сложной структурой зависимостей
  • Глобальные константы — простейшая альтернатива для действительно уникальных ресурсов (например, менеджер аудиосистемы)

Рекомендации по выбору

  1. Для моделей данных и состояния — используйте Dependency Injection или Environment Objects
  2. Для системных ресурсов (FileManager, UserDefaults) — используйте существующие синглтоны Apple или создавайте wrapper с DI
  3. Для кеширования — рассмотрите использование NSCache или специализированных структур данных
  4. Для тестирования — всегда предпочитайте подходы, позволяющие легко подменять реализации (DI, протоколы)

Главный принцип: единственный экземпляр должен управляться на уровне композиции приложения, а не внутри самого класса. Это сохраняет гибкость архитектуры и упрощает тестирование.

Как реализовать единственный экземпляр без использования Singleton? | PrepBro