Мастер-класс по dip
TRANSCRIPT
class AppDelegate {
let container = DependencyContainer(configBlock: configureContainers)
}
func configureContainers(root: DependencyContainer) { _ = moduleA ...}
let moduleA = DependencyContainer() { container in ...}
...
container.register(.unique) { ServiceImp() as Service } |_____| |___________| |_______| scope конструктор регистрируемый тип
container.register(.unique, type: Service, factory: ServiceImp.init) |_____| |_______| |______________| scope регистрируемый конструктор тип
Constructor injec-on
container.register() { try ServiceImp( repository: container.resolve() ) as Service}
Property injec-oncontainer.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}
Аргументы
container.register() { (url: NSURL) in ServiceImp(url: url) as Service}
container.register() { ServiceImp(repository: $0) as Service}
enum ComponentScope { case unique case shared //default case singleton case eagerSingleton case weakSingleton}
let service = try! container.resolve() as Service
let service = try! container.resolve(arguments: NSURL(...)) as Service
let repository = try! container.resolve() as ServiceRepositorylet service = try! container.resolve(arguments: repository) as Service
Сториборды
1. import DipUI
2. extension ViewController: StoryboardInstantiatable { }
3. container.register(tag: "myVC") { ViewController() }
4. DependencyContainer.uiContainers = [container]
Auto-wiringclass 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
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
Op#onalsclass ServiceImp: Service { init(repository: Repository?) { ... }}
container.register() { ServiceImp(repository: $0) as Service }container.register() { RepositoryImp() as Repository }
let service = try! container.resolve() as Service
Type forwarding
class ServiceImp: ServiceA, ServiceB { ... }
container.register() { ServiceImp() as ServiceA } .implements(ServiceB.self) .implements(ServiceC.self)
container.resolve() as ServiceA --> ServiceImpcontainer.resolve() as ServiceB --> ServiceImp
try! container.resolve() as ServiceC //fatal error
Achtung 1
container.register() { ServiceImp() as ServiceA } .implements(ServiceB.self)
container.register() { ServiceImp1() as ServiceA }container.register() { ServiceImp2() as ServiceB }
container.resolve() as ServiceA --> ServiceImp1container.resolve() as ServiceB --> ServiceImp2
Achtung 2class 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
Achtung 3
extension ViewController: StoryboardInstantiatable { }
DependencyContainer.uiContainers = [container]
Создавать контейнер до didFinishLaunching, в идеале в init AppDelegate
pros
• типичное API
• сториборды
cons
• слабая типизация, хоть и на дженериках
• бойлерплейт при оборачивании в интерфейс фабрики
• сложная реализация - пришлось изобретать auto-wiring и type-forwarding
Альтернатива(см. 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 }) ... }
}
pros
• Упрощает API
• Упрощает реализацию
• Фабричный интерфейс из коробки
• Более читаемо
cons
• cториборды
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() }}
/** @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") }}
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) }}
implements TypeName, ... - вторичные типы
factory Name - имя фабрики
name Name - имя фабричного метода
inject [TypeName] - тип свойства
tag, scope, storyboardInstantiatable и др.
• дополнительная документация
• легко синхронизировать с кодом
• ошибки видны при компиляции
• генерирует весь необходимый бойлерплейт