Какие паттерны в iOS/Swift позволяют избежать передачи множества зависимостей через инициализатор?

«Какие паттерны в iOS/Swift позволяют избежать передачи множества зависимостей через инициализатор?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Проблема: Передача большого числа сервисов (Network, Database, Analytics) через init приводит к раздуванию конструктора и сложности тестирования.

Решение — паттерны управления зависимостями:

  1. Контейнер внедрения зависимостей (DI Container):

    • Принцип: Централизованный реестр создает и предоставляет зависимости.
    • Пример с Swinject:

      let container = Container()
      container.register(NetworkService.self) { _ in NetworkServiceImpl() }
      container.register(DatabaseService.self) { _ in DatabaseServiceImpl() }
      
      class ViewModel {
          let network: NetworkService
          let database: DatabaseService
      
          init(resolver: Resolver) {
              self.network = resolver.resolve(NetworkService.self)!
              self.database = resolver.resolve(DatabaseService.self)!
          }
      }
  2. Локатор служб (Service Locator):

    • Принцип: Глобальный или контекстный объект, который возвращает требуемые сервисы по запросу.
    • Пример:

      class ServiceLocator {
          static let shared = ServiceLocator()
          private var services: [String: Any] = [:]
      
          func register<T>(_ service: T) {
              services["(T.self)"] = service
          }
      
          func resolve<T>() -> T? {
              return services["(T.self)"] as? T
          }
      }
  3. @EnvironmentObject (SwiftUI):

    • Принцип: Объект, внедренный в иерархию представлений и доступный всем дочерним view.
    • Пример:

      class AppDependencies: ObservableObject { /* сервисы */ }
      
      // В корневом View
      ContentView().environmentObject(AppDependencies())
      
      // В дочернем View
      struct ChildView: View {
          @EnvironmentObject var deps: AppDependencies
      }

Рекомендация: DI Container предпочтительнее для сложных проектов, так как он делает зависимости явными и облегчает модульное тестирование. Service Locator считается антипаттерном в некоторых случаях, так как скрывает зависимости.