Slide 1

Slide 1 text

Dependency Injection in iOS with Swinject by Oron Ben Zvi

Slide 2

Slide 2 text

Mobile Architect @Gett iOS Developer Fullstack Web Developer DevOps Engineer Oron Ben Zvi oronbz

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

What’s wrong with this code? class WeatherViewController: UIViewController { func load() { let weatherService = WeatherService.sharedInstance weatherService.loadData { ... } } }

Slide 5

Slide 5 text

What’s wrong with this code? class WeatherViewController: UIViewController { func load() { let weatherService = WeatherService.sharedInstance weatherService.loadData { ... } } }

Slide 6

Slide 6 text

What is dependency injection? In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. - Wikipedia Another Object Object Dependencies

Slide 7

Slide 7 text

But… What is a dependency? A dependency is an object that your class under test interacts with. Unit Test Class D1 D2 D3 Test

Slide 8

Slide 8 text

A real world example Guy under test

Slide 9

Slide 9 text

A real world example Guy under test Evaluates The tester

Slide 10

Slide 10 text

A real world example Guy under test Evaluates The tester Needs The dependency

Slide 11

Slide 11 text

A real world example Creates The tester The dependency

Slide 12

Slide 12 text

A real world example Creates The tester The dependency Guy under test Passes it to (Injecting) Interacts With

Slide 13

Slide 13 text

Service Locator (is not DI) Locator Class A Uses Service A Service B Locates Locates

Slide 14

Slide 14 text

Service Locator class ServiceLocator { static let sharedInstance = ServiceLocator() func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } // other services } class WeatherViewController: UIViewController { func load() { let weatherService = ServiceLocator.sharedInstance.weatherService weatherService.loadData { ... } } }

Slide 15

Slide 15 text

Service Locator class ServiceLocator { static let sharedInstance = ServiceLocator() func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } // other services } class WeatherViewController: UIViewController { func load() { let weatherService = ServiceLocator.sharedInstance.weatherService weatherService.loadData { ... } } }

Slide 16

Slide 16 text

Why it’s an Anti-Pattern? class ServiceLocator { static let sharedInstance = ServiceLocator() func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } // other services } class WeatherViewController: UIViewController { func load() { let weatherService = ServiceLocator.sharedInstance.weatherService weatherService.loadData { ... } } }

Slide 17

Slide 17 text

Why it’s an Anti-Pattern? class ServiceLocator { static let sharedInstance = ServiceLocator() func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } // other services } class WeatherViewController: UIViewController { func load() { let weatherService = ServiceLocator.sharedInstance.weatherService weatherService.loadData { ... } } }

Slide 18

Slide 18 text

Why it’s an Anti-Pattern? class ServiceLocator { static let sharedInstance = ServiceLocator() func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } // other services } class WeatherViewController: UIViewController { func load() { let weatherService = ServiceLocator.sharedInstance.weatherService weatherService.loadData { ... } } }

Slide 19

Slide 19 text

Why it’s an Anti-Pattern? class ServiceLocator { static let sharedInstance = ServiceLocator() func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } // other services } class WeatherViewController: UIViewController { func load() { let weatherService = ServiceLocator.sharedInstance.weatherService weatherService.loadData { ... } } }

Slide 20

Slide 20 text

Dependency Injection Patterns Extract and Override injection Method injection Property injection Constructor injection

Slide 21

Slide 21 text

Extract and Override Injection class WeatherViewController: UIViewController { func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } func load() { let weatherService = weatherService() weatherService.loadData { ... } } }

Slide 22

Slide 22 text

Method Injection class WeatherViewController: UIViewController { func load(weatherService: WeatherServicing) { weatherService.loadData { ... } } }

Slide 23

Slide 23 text

Property Injection class WeatherViewController: UIViewController { var weatherService: WeatherServicing? func load() { weatherService?.loadData { ... } } }

Slide 24

Slide 24 text

Constructor (Initializer) Injection class WeatherViewController: UIViewController { var weatherService: WeatherServicing init(weatherService: WeatherServicing) { self.weatherService = weatherService } func load() { weatherService.loadData { ... } } }

Slide 25

Slide 25 text

So who does create objects? • Creates UI & business logic objects • Inject dependencies • UI & business logic classes don’t know about assembly Assembly

Slide 26

Slide 26 text

Assembly class WeatherViewController: UIViewController { init(weatherService: WeatherServicing) {...} } ... class Assembly { func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } func weatherViewController() -> WeatherViewController { return WeatherViewController( weatherService: weatherService()) } }

Slide 27

Slide 27 text

Where assembly starts? class AppDelegate: UIResponder, UIApplicationDelegate { private let assembly = Assembly() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ...) -> Bool { ... window?.rootViewController = assembly.weatherViewController() ... } }

Slide 28

Slide 28 text

Assembly • How would you handle transitions from controller A to B? • Who creates controller B? • How would you give different implementations for different classes? • How to initialize controllers from storyboards? • Who decide the object scopes (singleton, multiple instances)?

Slide 29

Slide 29 text

Swinject Swinject is a lightweight dependency injection framework for Swift.

Slide 30

Slide 30 text

Swinject Basics protocol Person { var name: String { get } } class PetOwner: Person { let name: String let pet: Animal init(name: String, pet: Animal) { self.name = name self.pet = pet } } protocol Animal { var name: String { get } } class Cat: Animal { let name: String init(name: String) { self.name = name } }

Slide 31

Slide 31 text

Swinject Basics let container = Container() container.register(Animal.self) { _ in Cat(name: "Mimi") } container.register(Person.self) { r in PetOwner(name: "Stephen", pet: r.resolve(Animal.self)!) }

Slide 32

Slide 32 text

Swinject Basics let animal = container.resolve(Animal.self)! let person = container.resolve(Person.self)! let pet = (person as! PetOwner).pet print(animal.name) // prints "Mimi" print(animal is Cat) // prints "true" print(person.name) // prints "Stephen" print(person is PetOwner) // prints "true" print(pet.name) // prints "Mimi" print(pet is Cat) // prints "true"

Slide 33

Slide 33 text

Named Registrations let container = Container() container.register(Animal.self, name: "cat") { _ in Cat(name: "Mimi") } container.register(Animal.self, name: "dog") { _ in Dog(name: "Hachi") }

Slide 34

Slide 34 text

Named Registrations let cat = container.resolve(Animal.self, name:"cat")! let dog = container.resolve(Animal.self, name:"dog")! print(cat.name) // prints "Mimi" print(cat is Cat) // prints "true" print(dog.name) // prints "Hachi" print(dog is Dog) // prints "true"

Slide 35

Slide 35 text

Registration with arguments container.register(Animal.self) { _, name in Horse(name: name) } container.register(Animal.self) { _, name, running in Horse(name: name, running: running) }

Slide 36

Slide 36 text

Registration with arguments let animal1 = container.resolve(Animal.self, argument: "Spirit")! print(animal1.name) // prints "Spirit" print((animal1 as! Horse).running) // prints “false" let animal2 = container.resolve(Animal.self, arguments: "Lucky", true)! print(animal2.name) // prints "Lucky" print((animal2 as! Horse).running) // prints "true"

Slide 37

Slide 37 text

Dependency Injection Patterns Extract and Override injection Method injection Property injection Constructor injection

Slide 38

Slide 38 text

Method Injection let container = Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in let owner = PetOwner3() owner.setPet(r.resolve(Animal.self)!) return owner }

Slide 39

Slide 39 text

Property Injection let container = Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in let owner = PetOwner2() owner.pet = r.resolve(Animal.self) return owner }

Slide 40

Slide 40 text

Constructor Injection let container = Container() container.register(Animal.self) { _ in Cat() } container.register(Person.self) { r in PetOwner(pet: r.resolve(Animal.self)!) }

Slide 41

Slide 41 text

Scopes Object scope is a configuration option to determine how an instance provided by a DI container is shared in the system.

Slide 42

Slide 42 text

Transient Class A Service A Service A Different instances Class B

Slide 43

Slide 43 text

Graph (default) Service B Service A Class A Same instance provided for both Class A and Service B Class B Service A Different instances

Slide 44

Slide 44 text

Container (Singleton) Service B Class A Single instance to rule them all Class B Service A

Slide 45

Slide 45 text

Weak Service B Class A Single instance to rule them all Until there are no longer strong references Class B Service A

Slide 46

Slide 46 text

Back to our example // inside AppDelegate didFinishLaunching let container = Container() container.register(WeatherServicing.self) { _ in WeatherService() } .inObjectScope(.container) // singleton container.register(UIViewController.self, name: "WeatherVC") { r in let weatherService = r.resolve(WeatherServicing.self)! return WeatherViewController(weatherService: weatherService) } let weatherVC = container.resolve(UIViewController.self, name: "WeatherVC") window?.rootViewController = weatherVC

Slide 47

Slide 47 text

Storyboards ➕

Slide 48

Slide 48 text

Swinject Storyboard extension SwinjectStoryboard { @objc class func setup() { defaultContainer.storyboardInitCompleted(WeatherViewController.self) { r, c in c.weatherService = r.resolve(WeatherServicing.self) } defaultContainer.register(WeatherServicing.self) { _ in WeatherService() }.inObjectScope(.container) } }

Slide 49

Slide 49 text

Worthy Mentions Typhoon: https://github.com/appsquickly/Typhoon Dip: https://github.com/AliSoftware/Dip

Slide 50

Slide 50 text

Where to go next? RxGitHub: https://github.com/oronbz/RxGithub SwinjectMVVMExample: https://github.com/Swinject/SwinjectMVVMExample Swinject Assembly: https://github.com/Swinject/Swinject/blob/master/Documentation/ Assembler.md Swinject Autoregistration: https://github.com/Swinject/SwinjectAutoregistration

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content