Мастер-класс по dip

40
Dip

Upload: rambler-ios

Post on 14-Apr-2017

132 views

Category:

Technology


0 download

TRANSCRIPT

Dip

2014 - Typhoon >>>> 2016 - Dip

h"ps://github.com/AliSo3ware/Dip

1. Создаём контейнеры

2. Регистрируем в них ВСЕ компоненты

3. Резолвим граф

1. Создаём контейнеры

class AppDelegate {

let container = DependencyContainer(configBlock: configureContainers)

}

func configureContainers(root: DependencyContainer) { _ = moduleA ...}

let moduleA = DependencyContainer() { container in ...}

...

1 модуль - 1 контейнер

moduleA.collaborate(with: moduleB, root)moduleB.collaborate(with: moduleA, root)

2. Регистрируем в них ВСЕ компоненты

container.register(.unique) { ServiceImp() as Service } |_____| |___________| |_______| scope конструктор регистрируемый тип

container.register(.unique, type: Service, factory: ServiceImp.init) |_____| |_______| |______________| scope регистрируемый конструктор тип

container.register(.unique) { ServiceImp() }

container.register(.unique, factory: ServiceImp.init)

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}

3. Резолвим граф

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 !

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

Проблемы register/resolve API

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ториборды

Кодогенераторh"ps://github.com/ilyapuchka/dipgen

/** @dip.register Service */class ServiceImp: Service { }

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 и др.

• дополнительная документация

• легко синхронизировать с кодом

• ошибки видны при компиляции

• генерирует весь необходимый бойлерплейт

!