Slide 1

Slide 1 text

!ZPJDIJUHZ Dependency Injection Pattern for iOS Apps Yoichi Tagaya FrenchKit, 20-21 September, 2018

Slide 2

Slide 2 text

!ZPJDIJUHZ Yoichi Tagaya, from Tokyo, Japan About me

Slide 3

Slide 3 text

!ZPJDIJUHZ Tokyo evening view 1IPUPCZ D 5PNP:VO IUUQXXXZVOQIPUPOFU

Slide 4

Slide 4 text

!ZPJDIJUHZ About me as Swinject author • Swinject: dependency injection framework for Swift • Maintained by 5 members • Recently by Jakub Vano especially! • 2,6k+ GitHub stars now and 1k clones everyday • 4 extensions provided

Slide 5

Slide 5 text

!ZPJDIJUHZ Agenda • Software architecture • Dependency injection (DI) • Swinject (DI container)

Slide 6

Slide 6 text

!ZPJDIJUHZ What is software architecture

Slide 7

Slide 7 text

!ZPJDIJUHZ What is software architecture IUUQTXXXJCNDPNEFWFMPQFSXPSLTSBUJPOBMMJCSBSZGFCFFMFTJOEFYIUNM

Slide 8

Slide 8 text

!ZPJDIJUHZ Peter Eeles says An architecture • Defines structure and behavior • Focuses on significant elements • May conform to an architectural style • Influences team structure (inverse Conway maneuver) • Balances stakeholder needs • Is influenced by its environment • Embodies decisions based on rationale

Slide 9

Slide 9 text

!ZPJDIJUHZ Architecture is not static but dynamic .7$ "SDIJUFDUVSF :FBST TJODFMBVODI .77. 7*1&3 &YBNQMFPGBQSPEVDU 3*#T &OHJOFFST 6TFST L . .

Slide 10

Slide 10 text

!ZPJDIJUHZ Architecture is not static but dynamic $ # " /PX $ /FX# " $ % -BUFS

Slide 11

Slide 11 text

!ZPJDIJUHZ Production / unit test $ # " 1SPEVDUJPO 6OJU5FTU .PDLTUVC PG# "

Slide 12

Slide 12 text

!ZPJDIJUHZ We need flexibility in architecture

Slide 13

Slide 13 text

!ZPJDIJUHZ How to make architecture flexible? Dependency injection!

Slide 14

Slide 14 text

!ZPJDIJUHZ Because dependency injection is glue but sticky-like glue 5JHIU$PVQMJOH -PPTF$PVQMJOH

Slide 15

Slide 15 text

!ZPJDIJUHZ How to make the sticky-like glue?

Slide 16

Slide 16 text

!ZPJDIJUHZ SOLID principles • S - Single responsibility principle • O - Open/closed principle • L - Liskov substitution principle • I - Interface segregation principle • D - Dependency inversion principle • Should not depend on concretions but abstractions

Slide 17

Slide 17 text

!ZPJDIJUHZ Depending on concretions / abstractions Depending on concretions (tight coupling) *NQMFNFOUBUJPO $PODSFUF5ZQF Depending on abstractions (loose coupling) 3FGFSFODF *NQMFNFOUBUJPO Protocol (Interface) 3FGFSFODF $PODSFUF5ZQF" $PODSFUF5ZQF#

Slide 18

Slide 18 text

!ZPJDIJUHZ Dependency injection: a way to realize dependency inversion principle *NQMFNFOUBUJPO Protocol (Interface) 3FGFSFODF *OTUBODFPG $PODSFUF5ZQF# 1BTT JOKFDU UPJOJUJBMJ[FS

Slide 19

Slide 19 text

!ZPJDIJUHZ Dependency injection: a way to realize dependency inversion principle *NQMFNFOUBUJPO Protocol (Interface) 3FGFSFODF $PODSFUF5ZQF# 5IFTZTUFNJTDPOpHVSFE UPVTF$PODSFUF5ZQF#

Slide 20

Slide 20 text

!ZPJDIJUHZ Code example of dependency injection protocol APIClientProtocol { ... } class APIClient: APIClientProtocol { ... } let vm = MyViewModel(apiClient: APIClient()) class MyViewModel { let apiClient: APIClientProtocol init(apiClient: APIClientProtocol) { self.apiClient = apiClient } } APIClientProtocol "CTUSBDUUZQF *OKFDUDPODSFUFUZQF

Slide 21

Slide 21 text

!ZPJDIJUHZ Try dependency injection with Swinject Register/resolve-style DI container like dictionary

Slide 22

Slide 22 text

!ZPJDIJUHZ How to use Swinject container $POUBJOFS Register Resolve

Slide 23

Slide 23 text

!ZPJDIJUHZ Code example of registration $POUBJOFS Register import Swinject let container = Container() container.register(MyViewModel.self) { resolver in let apiClient = resolver.resolve(APIClientProtocol.self)! return MyViewModel(apiClient: apiClient) } container.register(APIClientProtocol.self) { _ in APIClient() }

Slide 24

Slide 24 text

!ZPJDIJUHZ Code example of resolution $POUBJOFS Resolve import Swinject let vm = container.resolve(MyViewModel.self)!

Slide 25

Slide 25 text

!ZPJDIJUHZ It’s ok for a simple case, but…

Slide 26

Slide 26 text

!ZPJDIJUHZ In a production app class MyViewModel { init(dep1: D1, dep2: D2, dep3: D3, id: String, name: String) { ... } } .BOZEFQFOEFODJFT 3VOUJNFQBSBNFUFST PUIFSUIBOEFQFOEFODJFT %FQFOEFODZJOKFDUJPODPEFJTPGUFOMJLFUIJT

Slide 27

Slide 27 text

!ZPJDIJUHZ Using typealias of dependency tuple init(dependency: Dependency, id: String, name: String) 5ZQFBMJBTPGUVQMF % % % %FQFOEFODZ 1BSBNFUFST

Slide 28

Slide 28 text

!ZPJDIJUHZ Example of class definition with typealias of tuple protocol APIClientProtocol { ... } class APIClient: APIClientProtocol { ... } protocol LoggerProtocol { ... } class Logger: LoggerProtocol { ... } 5ZQFBMJBTPG EFQFOEFODZUVQMF class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol ) let (apiClient, logger): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger) = dependency self.id = id } } &YBNQMFXJUIEFQFOEFODJFTBOEQBSBNFUFS

Slide 29

Slide 29 text

!ZPJDIJUHZ DI Registration let container = Container() container.register(APIClientProtocol.self) { _ in APIClient() } container.register(LoggerProtocol.self) { _ in Logger() } $POUBJOFS Register container.register(MyViewModel.self) { (resolver, id: String) in let dependency = ( resolver.resolve(APIClientProtocol.self)!, resolver.resolve(LoggerProtocol.self)! ) return MyViewModel( dependency: dependency, id: id ) }

Slide 30

Slide 30 text

!ZPJDIJUHZ DI Resolution let myViewModel = container.resolve( MyViewModel.self, argument: "abc")! $POUBJOFS Resolve

Slide 31

Slide 31 text

!ZPJDIJUHZ Easier to see the dependencies and parameters!

Slide 32

Slide 32 text

!ZPJDIJUHZ Let’s add another dependency to implement a new feature

Slide 33

Slide 33 text

!ZPJDIJUHZ Add another dependency let (apiClient, logger): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger) = dependency self.id = id } } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol )

Slide 34

Slide 34 text

!ZPJDIJUHZ Add another dependency protocol DatabaseProtocol { ... } class Database: DatabaseProtocol { ... } let (apiClient, logger): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger) = dependency self.id = id } } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol )

Slide 35

Slide 35 text

!ZPJDIJUHZ Add another dependency protocol DatabaseProtocol { ... } class Database: DatabaseProtocol { ... } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol, DatabaseProtocol ) let (apiClient, logger): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger) = dependency self.id = id } }

Slide 36

Slide 36 text

!ZPJDIJUHZ Add another dependency protocol DatabaseProtocol { ... } class Database: DatabaseProtocol { ... } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol, DatabaseProtocol ) let (apiClient, logger): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger) = dependency self.id = id } } 8SPOHUVQMFIFSF ❕

Slide 37

Slide 37 text

!ZPJDIJUHZ Add another dependency protocol DatabaseProtocol { ... } class Database: DatabaseProtocol { ... } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol, DatabaseProtocol ) let (apiClient, logger, database): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger) = dependency self.id = id } } 8SPOHUVQMFIFSF ❕

Slide 38

Slide 38 text

!ZPJDIJUHZ Add another dependency protocol DatabaseProtocol { ... } class Database: DatabaseProtocol { ... } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol, DatabaseProtocol ) let (apiClient, logger, database): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger, database) = dependency self.id = id } } 8SPOHUVQMFIFSF ❕

Slide 39

Slide 39 text

!ZPJDIJUHZ Add another dependency protocol DatabaseProtocol { ... } class Database: DatabaseProtocol { ... } class MyViewModel { typealias Dependency = ( APIClientProtocol, LoggerProtocol, DatabaseProtocol ) let (apiClient, logger, database): Dependency let id: String init(dependency: Dependency, id: String) { (apiClient, logger, database) = dependency self.id = id } }

Slide 40

Slide 40 text

!ZPJDIJUHZ Add another dependency container.register(MyViewModel.self) { (resolver, id: String) in let dependency = ( resolver.resolve(APIClientProtocol.self)!, resolver.resolve(LoggerProtocol.self)! ) return MyViewModel( dependency: dependency, id: id ) } let myViewModel = container.resolve( MyViewModel.self, argument: "abc")! $BOOPUDPOWFSU ❕

Slide 41

Slide 41 text

!ZPJDIJUHZ Add another dependency container.register(MyViewModel.self) { (resolver, id: String) in let dependency = ( resolver.resolve(APIClientProtocol.self)!, resolver.resolve(LoggerProtocol.self)! ) return MyViewModel( dependency: dependency, id: id ) } let myViewModel = container.resolve( MyViewModel.self, argument: "abc")! container.register(DatabaseProtocol.self) { _ in Database() } $BOOPUDPOWFSU ❕

Slide 42

Slide 42 text

!ZPJDIJUHZ Add another dependency container.register(DatabaseProtocol.self) { _ in Database() } $BOOPUDPOWFSU ❕ container.register(MyViewModel.self) { (resolver, id: String) in let dependency = ( resolver.resolve(APIClientProtocol.self)!, resolver.resolve(LoggerProtocol.self)!, resolver.resolve(DatabaseProtocol.self)! ) return MyViewModel( dependency: dependency, id: id ) } let myViewModel = container.resolve( MyViewModel.self, argument: "abc")!

Slide 43

Slide 43 text

!ZPJDIJUHZ Add another dependency container.register(DatabaseProtocol.self) { _ in Database() } container.register(MyViewModel.self) { (resolver, id: String) in let dependency = ( resolver.resolve(APIClientProtocol.self)!, resolver.resolve(LoggerProtocol.self)!, resolver.resolve(DatabaseProtocol.self)! ) return MyViewModel( dependency: dependency, id: id ) } let myViewModel = container.resolve( MyViewModel.self, argument: "abc")!

Slide 44

Slide 44 text

!ZPJDIJUHZ Just follow what Swift compiler says to add new dependencies!

Slide 45

Slide 45 text

!ZPJDIJUHZ Summary • Architecture is not static but dynamic to evolve. • Dependency injection is sticky-like glue. • Use a typealias to group dependencies. • Just follow Swift compiler to add new dependencies.

Slide 46

Slide 46 text

!ZPJDIJUHZ More info, merci beaucoup! • Swinject Tutorial for iOS: Getting Started by Gemma Barlow at raywenderlich.com • iOS App Development: Design Patterns for Mobile Architecture by Jon Bott at lynda.com • Dependency injection in Swift by Manuel Munoz at blog.quadiontech.com • Adventures in Dependency Swinjection by Ben Dietzkis at medium.com/@topLayoutGuide • Swinject - Dependency Injection in iOS by Iron Ben Zvi at speakerdeck.com/oronbz