Swinject — Dependency Injection контейнер для Swift (iOS/macOS)
Dependency Injection (DI) — один из ключевых принципов SOLID, позволяющий создавать слабосвязанный, тестируемый и масштабируемый код. В мире Swift для iOS и macOS разработки одной из самых популярных библиотек, реализующих DI, является Swinject. Это легковесный, но мощный контейнер внедрения зависимостей, который упрощает управление объектами и их жизненным циклом.
В этой статье мы подробно разберём, как работает Swinject, как его установить и использовать в реальных проектах.
Что такое Dependency Injection и зачем нужен контейнер?
Внедрение зависимостей — это техника, при которой объект получает свои зависимости извне, а не создаёт их сам. Это повышает гибкость и упрощает тестирование. Контейнер DI (например, Swinject) автоматизирует процесс создания и связывания объектов, избавляя разработчика от написания boilerplate-кода.
Установка Swinject
Swinject можно установить с помощью Swift Package Manager, CocoaPods или Carthage. Рассмотрим самый современный способ — SPM:
// В Xcode: File → Swift Packages → Add Package Dependency
// URL: https://github.com/Swinject/Swinject.git
// Версия: от 2.8.0 и выше
Или добавьте в Package.swift:
dependencies: [
.package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")
]
Основные компоненты Swinject
- Container — основной объект, в котором регистрируются и разрешаются зависимости.
- Service — тип (протокол или класс), который мы хотим внедрить.
- Registration — процесс связывания абстракции (протокола) с конкретной реализацией.
- Scope — управляет жизненным циклом объекта (транзиентный, синглтон, и т.д.).
Базовый пример использования
Предположим, у нас есть протокол NetworkServiceProtocol и его реализация NetworkService:
import Swinject
protocol NetworkServiceProtocol {
func fetchData() -> String
}
class NetworkService: NetworkServiceProtocol {
func fetchData() -> String {
return "Данные получены"
}
}
// Создаём контейнер
let container = Container()
// Регистрируем зависимость
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
// Разрешаем (получаем) экземпляр
let service = container.resolve(NetworkServiceProtocol.self)!
print(service.fetchData()) // "Данные получены"
Внедрение зависимостей в ViewController (iOS)
Чаще всего Swinject используется для внедрения сервисов в контроллеры. Рассмотрим пример с вью-моделью:
import Swinject
// Протокол и реализация вью-модели
protocol UserViewModelProtocol {
func getUsers() -> [String]
}
class UserViewModel: UserViewModelProtocol {
private let networkService: NetworkServiceProtocol
init(networkService: NetworkServiceProtocol) {
self.networkService = networkService
}
func getUsers() -> [String] {
// Используем networkService для загрузки
return ["Alice", "Bob"]
}
}
// Регистрация в контейнере
container.register(UserViewModelProtocol.self) { resolver in
let network = resolver.resolve(NetworkServiceProtocol.self)!
return UserViewModel(networkService: network)
}
// Использование в контроллере
class UsersViewController: UIViewController {
var viewModel: UserViewModelProtocol?
override func viewDidLoad() {
super.viewDidLoad()
viewModel = container.resolve(UserViewModelProtocol.self)
print(viewModel?.getUsers() ?? [])
}
}
Жизненные циклы объектов (Scopes)
Swinject поддерживает несколько scopes:
- .transient — каждый раз создаётся новый экземпляр (по умолчанию).
- .container — синглтон в рамках одного контейнера.
- .graph — экземпляр живёт только во время разрешения графа зависимостей.
Пример с синглтоном:
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
.inObjectScope(.container) // теперь это синглтон
Именованные регистрации
Если нужно зарегистрировать несколько реализаций одного протокола, используйте имена:
container.register(NetworkServiceProtocol.self, name: "mock") { _ in MockNetworkService() }
container.register(NetworkServiceProtocol.self, name: "real") { _ in NetworkService() }
let mockService = container.resolve(NetworkServiceProtocol.self, name: "mock")
Assembly — модульная организация кода
Для больших проектов удобно группировать регистрации в отдельные классы — Assembly: