# iOS-Инкубатор. Нюансы и антипаттерны DI. Что не так с Service Locator?
15 ноября 2022
## Одиночество
Посмотрите на этот скриншот, мы видим, что тут вызывается в запись в лог через одиночку. И как видно в коде масса таких мест, обратите внимание на мини-карту.
А теперь представьте, что будет при необходимости замены системы логирования.

- Нарушает _принцип единственной ответственности класса_.
- Маскирует плохой дизайн.
- Проблемы мультипоточности.
- Проблемы при юнит-тестировании.
Зависимость обычного класса от синглтона не видна в публичном контракте класса!
Одиночку нужно передавать как зависимость через DI:
```swift
/// Сборщик модуля Actualization
final class ActualizationAssembly: IActualizationAssembly {
func makeModule(coordinator: IMainCoordinator) -> ActualizationViewController {
let viewController = ActualizationViewController()
viewController.presenter = ActualizationPresenter(
coordinator: coordinator,
hardwareHelper: ApplicationCoreServicesContainer.shared.hardwareHelper,
appGroupManager: ApplicationCoreServicesContainer.shared.appGroupManager,
taskManager: ApplicationCoreServicesContainer.taskManager,
logger: Logger.shared,
dialogController: ApplicationCoreServicesContainer.shared.dialogController,
bannerController: ApplicationCoreServicesContainer.shared.bannerController
)
return viewController
}
}
```
И еще лучше, закрывать зависимости протоколами:
```swift
init(
coordinator: IMainCoordinator,
hardwareHelper: IHardwareHelper,
appGroupManager: SMAppGroupManager,
taskManager: ITaskManager,
logger: ILogger,
dialogController: IDialogController,
bannerController: IBannerController
)
```
Но ни в коем случае не делать со значениями по умолчанию, так как это тоже не видно в публичном контракте класса:
```swift
init(
coordinator: IMainCoordinator,
hardwareHelper: IHardwareHelper = ApplicationCoreServicesContainer.shared.hardwareHelper,
appGroupManager: SMAppGroupManager = ApplicationCoreServicesContainer.shared.appGroupManager,
taskManager: ITaskManager = ApplicationCoreServicesContainer.taskManager,
logger: ILogger = Logger.shared,
dialogController: IDialogController = ApplicationCoreServicesContainer.shared.dialogController,
bannerController: IBannerController = ApplicationCoreServicesContainer.shared.bannerController
)
```
Используй Одиночку только при наличии веских аргументов для наличия единственного экземпляра класса.
## DI Container
Давайте рассмотрим одну из реализаций DI контейнера, который поддерживает кэширование:
```swift
/// Контейнер зависимостей: сервисов или мэнеджеров
final class DIContainer: IDIContainer {
// MARK: - Private Properties
private var cachedServices: [String: Any] = [:]
// MARK: - IDIContainer implementation
func resolve<T>(_ make: @autoclosure () -> T, onMake: ((T) -> Void)? = nil) -> T {
if let cachedService = getCachedService(T.self) {
return cachedService
} else {
let newService = make()
onMake?(newService)
cache(newService)
return newService
}
}
}
// MARK: - Private Methods
private extension DIContainer {
func cache<T>(_ newService: T) {
cachedServices[String(describing: T.self)] = newService
}
func getCachedService<T>(_: T.Type) -> T? {
let key = String(describing: T.self)
if let service = cachedServices[key] as? T {
return service
} else {
return nil
}
}
}
```
```swift
// Assembly для зависимостей приложения
protocol IAppDependenciesAssembly {
/// Сервис генерации ссылок на изображения товаров
func resolve(_: IImageUrlGenerator.Protocol) -> IImageUrlGenerator
/// Сервис для хранения настроек
func resolve(_: IUserDefaultsService.Protocol) -> IUserDefaultsService
/// Менеджер событий приложения
func resolve(_: IAppEventsManager.Protocol) -> IAppEventsManager
/// Обработчик событий приложения
func resolve(_: IAppEventsHandler.Protocol) -> IAppEventsHandler
}
extension IAppDependenciesAssembly where Self: IAssembly {
func resolve(_: IImageUrlGenerator.Protocol) -> IImageUrlGenerator {
let imageUrlGenerator = ImageUrlGenerator()
return container.resolve(imageUrlGenerator)
}
func resolve(_: IUserDefaultsService.Protocol) -> IUserDefaultsService {
return container.resolve(UserDefaultsService())
}
func resolve(_: IAppEventsHandler.Protocol) -> IAppEventsHandler {
container.resolve(AppEventsManager()) as AppEventsManager
}
func resolve(_: IAppEventsManager.Protocol) -> IAppEventsManager {
container.resolve(AppEventsManager()) as AppEventsManager
}
}
```
Пример использования такого контейнера:
```swift
final class SearchScreenAssembly: ISearchScreenAssembly {
func makeModule(
assembly: IAssembly,
mainFlowAssembly: IMainFlowAssembly,
coordinator: IMainCoordinator
) -> UIViewController {
let presenter = SearchPresenter(
wareService: mainFlowAssembly.resolve(IWareService.self),
hardwareService: assembly.resolve(IHardwareService.self),
appContext: mainFlowAssembly.resolve(IAppContext.self),
imageUrlGenerator: assembly.resolve(IImageUrlGenerator.self)
)
let viewController = SearchViewController.instantiate()
viewController.setup(
appContext: mainFlowAssembly.resolve(IAppContext.self),
coordinator: coordinator,
presenter: presenter
)
presenter.setup(view: viewController)
return viewController
}
}
```
Я вляется ли DI Container антипаттерном?
- Да, если его передавать в качестве зависимости, откуда потом класс сам добывает свои зависимости.
- Да, ясли он не обеспечивает безопасности типов.
- Нет, если его применять внутри Root Composition.
## Антипаттерн ServiceLocator
Основная идея **Service Locator** заключается в том, что в программе имеется объект, знающий, как получить все зависимости (сервисы), которые могут потребоваться.
```swift
class ServiceLocator {
var services: [Any: Any]()
func register<T>(_: T.Self) {
...
}
func get<T>() -> T {
...
}
}
```

Этот шаблон используется для регистрации сервисов для какого-то класса. Вы регистрируете их в словаре и теперь, можно выдать любой класс.
Но при таком подходе теряется безопасность типов, так как теперь на запрос сервиса разработчик получает неизвестно что.
Лучше используйте абстрактную фабрику.
## Антипаттерн Context
Context Anti-pattern – это глобальные переменные среды выполнения приложения. Значительно похож на синглтон, но с изменяемым состоянием. Он имеет общий глобальный экземпляр, который может быть заменен в процессе работы.
Описание нашего контекста:
```swift
struct Context {
/// login текущего авторизованного пользователя.
var userName: () -> String = { "" }
/// Номер магазина в котором работает устройство.
var shopNumber: () -> Int = { 0 }
/// Торговая сеть в котором работает устройство.
var shopRetailer: () -> Retailer = { .some }
}
```
Объявление в AppDelegate.swift:
```swift
#if DEBUG
var context = Context()
#else
let context = Context()
#endif
```
Замена на необходимые значения для разработки:
```swift
...
context.userName = { "leonovka" }
context.shopNumber = { 1 }
...
```
Его использование проникает глубоко в код, как и одиночка. Затрудняет тестирование.

## @Environment как Антипаттерн
Это Property Wrappers от _SwiftUI_.
```swift
import SwiftUI
struct SomeView: View {
let title: String
...
@Environment var time: Date
...
}
```
Такой код скомпилируется даже без установки time, но во время выполнение в этих условиях произойдет падение.
Мы теряем безопасность типов.
## Стрелка
Антипаттерн Стрелка - это когда в коде много Bool флажков и мы вынуждены их все проверять, изза чего наш код выглядит как стрелки.
Также это бывает, когда у нас асинхронные запросы и они вкладываются друг в друга.

Решение
- Не писать так. Стрелку можно развернуть из середины, вынося код в отдельные методы.
- Можно применяя протоколы подсовывать нужную реализацию нашей бизнес-логики. Например использовать шаблон Стратегия.
## Полезные ссылки
- https://www.pointfree.co/blog/posts/21-how-to-control-the-world
- https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
- https://blog.ploeh.dk/2010/11/01/PatternRecognitionAbstractFactoryorServiceLocator/