Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swinject - Dependency Injection in iOS

Swinject - Dependency Injection in iOS

In this lecture we'll dive together into how simple and straightforward it is to use dependency injection to clean up your classes, make sure your classes remain as easy-to-test as possible while truly embracing protocol oriented programming.

Oron Ben Zvi

January 30, 2018
Tweet

Other Decks in Technology

Transcript

  1. What’s wrong with this code? class WeatherViewController: UIViewController { func

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

    load() { let weatherService = WeatherService.sharedInstance weatherService.loadData { ... } } }
  3. 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
  4. But… What is a dependency? A dependency is an object

    that your class under test interacts with. Unit Test Class D1 D2 D3 Test
  5. A real world example Creates The tester The dependency Guy

    under test Passes it to (Injecting) Interacts With
  6. 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 { ... } } }
  7. 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 { ... } } }
  8. 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 { ... } } }
  9. 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 { ... } } }
  10. 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 { ... } } }
  11. 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 { ... } } }
  12. Extract and Override Injection class WeatherViewController: UIViewController { func weatherService()

    -> WeatherServicing { return WeatherService.sharedInstance } func load() { let weatherService = weatherService() weatherService.loadData { ... } } }
  13. Constructor (Initializer) Injection class WeatherViewController: UIViewController { var weatherService: WeatherServicing

    init(weatherService: WeatherServicing) { self.weatherService = weatherService } func load() { weatherService.loadData { ... } } }
  14. So who does create objects? • Creates UI & business

    logic objects • Inject dependencies • UI & business logic classes don’t know about assembly Assembly
  15. Assembly class WeatherViewController: UIViewController { init(weatherService: WeatherServicing) {...} } ...

    class Assembly { func weatherService() -> WeatherServicing { return WeatherService.sharedInstance } func weatherViewController() -> WeatherViewController { return WeatherViewController( weatherService: weatherService()) } }
  16. Where assembly starts? class AppDelegate: UIResponder, UIApplicationDelegate { private let

    assembly = Assembly() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ...) -> Bool { ... window?.rootViewController = assembly.weatherViewController() ... } }
  17. 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)?
  18. 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 } }
  19. 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)!) }
  20. 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"
  21. 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") }
  22. 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"
  23. Registration with arguments container.register(Animal.self) { _, name in Horse(name: name)

    } container.register(Animal.self) { _, name, running in Horse(name: name, running: running) }
  24. 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"
  25. 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 }
  26. 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 }
  27. Constructor Injection let container = Container() container.register(Animal.self) { _ in

    Cat() } container.register(Person.self) { r in PetOwner(pet: r.resolve(Animal.self)!) }
  28. Scopes Object scope is a configuration option to determine how

    an instance provided by a DI container is shared in the system.
  29. Graph (default) Service B Service A Class A Same instance

    provided for both Class A and Service B Class B Service A Different instances
  30. Weak Service B Class A Single instance to rule them

    all Until there are no longer strong references Class B Service A
  31. 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
  32. 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) } }
  33. 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