This document provides an overview of Dip, a dependency injection framework for Swift. It discusses key Dip concepts like dependency containers, component registration, and resolution. Key points include:
1. Modules are represented as dependency containers that manage their own components.
2. All components are registered in their containers with their dependencies.
3. The dependency graph is resolved by obtaining fully-initialized components from their containers.
14. Property injec-on
container.register() {
let service = ServiceImp()
service.repository = try container.resolve() as ServiceRepository
return service as Service
}
//или
container.register() { ServiceImp() as Service }
.resolvingProperties { container, service in
service.repository = try container.resolve() as ServiceRepository
}
18. let service = try! container.resolve() as Service
let service = try! container.resolve(arguments: NSURL(...)) as Service
let repository = try! container.resolve() as ServiceRepository
let service = try! container.resolve(arguments: repository) as Service
21. Auto-wiring
class ServiceImp: Service {
init(repository: Repository) { ... }
}
container.register() { ServiceImp(repository: $0) as Service }
//или
container.register(Service.self, factory: ServiceImp.init)
container.register() { RepositoryImp() as Repository }
let service = try! container.resolve() as Service
22. Auto-injec+on
class ServiceImp: Service {
let repository = Injected<Repository>()
}
container.register() { ServiceImp() as Service }
container.register() { RepositoryImp() as Repository }
let service = try! container.resolve() as Service
23. Op#onals
class ServiceImp: Service {
init(repository: Repository?) { ... }
}
container.register() { ServiceImp(repository: $0) as Service }
container.register() { RepositoryImp() as Repository }
let service = try! container.resolve() as Service
24. Type forwarding
class ServiceImp: ServiceA, ServiceB { ... }
container.register() { ServiceImp() as ServiceA }
.implements(ServiceB.self)
.implements(ServiceC.self)
container.resolve() as ServiceA --> ServiceImp
container.resolve() as ServiceB --> ServiceImp
try! container.resolve() as ServiceC //fatal error
26. Achtung 1
container.register() { ServiceImp() as ServiceA }
.implements(ServiceB.self)
container.register() { ServiceImp1() as ServiceA }
container.register() { ServiceImp2() as ServiceB }
container.resolve() as ServiceA --> ServiceImp1
container.resolve() as ServiceB --> ServiceImp2
27. Achtung 2
class ServiceImp: Service {
init(url: NSURL?) { ... }
}
container.register() { (url: NSURL?) in ServiceImp(url: url) as Service }
let url: NSURL = ...
try! container.resolve(arguments: url) as Service //fatal error
let url: NSURL? = ...
try! container.resolve(arguments: url) as Service
28. Achtung 3
extension ViewController: StoryboardInstantiatable { }
DependencyContainer.uiContainers = [container]
Создавать контейнер до didFinishLaunching, в идеале в init
AppDelegate
30. pros
• типичное API
• сториборды
cons
• слабая типизация, хоть и на дженериках
• бойлерплейт при оборачивании в интерфейс фабрики
• сложная реализация - пришлось изобретать auto-wiring и
type-forwarding
31. Альтернатива
(см. h'ps://github.com/jkolb/FieryCrucible)
class Module: DependencyFactory {
func wireframe() -> ModuleWireframe {
shared(
factory: { Wireframe() as ModuleWireframe },
configure: { wireframe in
wireframe.interactor = self.interactor()
}
}
func interactor() -> ModuleInteractor {
shared({ Interactor() as ModuleInteractor }) ...
}
}
32. pros
• Упрощает API
• Упрощает реализацию
• Фабричный интерфейс из коробки
• Более читаемо
cons
• cториборды
35. Dip.base.swi*
let baseContainer = DependencyContainer { container in
unowned let container = container
container.register(factory: ServiceImp.init)
}
class BaseFactory {
private let container: DependencyContainer
init(container: DependencyContainer = baseContainer) {
self.container = container
}
func service() -> Service {
return container.resolve()
}
}
36. /**
@dip.factory List
@dip.constructor init(nibName:bundle:)
*/
class ListViewController: UIViewController {
/**@dip.arguments nibName*/
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
37. Dip.list.swi)
let listContainer = DependencyContainer { container in
unowned let container = container
container.register(factory: { nibName in
try ListViewController.init(nibName: nibName, bundle: container.resolve())
})
}
class ListFactory {
private let container: DependencyContainer
init(container: DependencyContainer = listContainer) {
self.container = container
}
func listViewController(nibName nibNameOrNil: String?) -> ListViewController {
return try! container.resolve(arguments: nibNameOrNil)
}
}
38. implements TypeName, ... - вторичные типы
factory Name - имя фабрики
name Name - имя фабричного метода
inject [TypeName] - тип свойства
tag, scope, storyboardInstantiatable и др.
39. • дополнительная документация
• легко синхронизировать с кодом
• ошибки видны при компиляции
• генерирует весь необходимый бойлерплейт