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

Dependency Injection in Swift

Ilya Puchka
May 24, 2016
1.2k

Dependency Injection in Swift

Ilya Puchka

May 24, 2016
Tweet

Transcript

  1. DEPENDENCY
    INJECTION (DI) IN
    SWIFT
    ILYA PUCHKA (@ILYAPUCHKA)
    IOS DEVELOPER @HELLOFRESH

    View Slide

  2. FUNCTIONAL
    OR
    OBJECT ORIENTED?

    View Slide

  3. SOLID KISS DRY
    YAGNI RAP CQS
    DECORATOR FACADE
    ABSTRACT FACTORY
    STRATEGY ...

    View Slide

  4. DI SOLID

    View Slide

  5. WHAT IS
    DEPENDENCY
    INJECTION?

    View Slide

  6. In software engineering,
    dependency injection is a
    software design pattern
    that implements inversion
    of control for resolving
    dependencies.
    — Wikipedia

    View Slide

  7. “Dependency injection is
    really just passing in an
    instance variable.
    — James Shore

    View Slide

  8. VOODO MAGIC
    SLOW FRAMEWORKS
    ONLY FOR TESTING
    OVERENGINEERING

    View Slide

  9. > What is Dependency Injection?
    > How to do it?
    > How not to do it?

    View Slide

  10. WHY DEPENDENCY
    INJECTION?

    View Slide

  11. ABSTRACTIONS
    EVERYWHERE

    View Slide

  12. LOOSE COUPLING

    View Slide

  13. TEST
    EXTEND AND REUSE
    DEVELOP IN PARALLER
    MAINTAIN

    View Slide

  14. NOT JUST UNIT TESTS
    BUT
    LOOSE COUPLING

    View Slide

  15. FIRST STEP -
    PASSING INSTANCE
    VARIABLES

    View Slide

  16. SECOND STEP - ?
    THIRD STEP - ?

    View Slide

  17. PATTERNS
    CUSTRUCTOR INJECTION
    PROPERTY INJECTION
    METHOD INJECTION
    AMBIENT CONTEXT

    View Slide

  18. CONSTRUCTOR INJECTION
    class NSPersistentStore : NSObject {
    init(persistentStoreCoordinator root: NSPersistentStoreCoordinator?,
    configurationName name: String?,
    URL url: NSURL,
    options: [NSObject: AnyObject]?)
    var persistentStoreCoordinator: NSPersistentStoreCoordinator? { get }
    }

    View Slide

  19. EASY TO IMPLEMENT
    IMMUTABILITY

    View Slide

  20. PROPERTY INJECTION
    extension UIViewController {
    weak public var transitioningDelegate:
    UIViewControllerTransitioningDelegate?
    }

    View Slide

  21. LOCAL DEFAULT
    FOREIGN DEFAULT

    View Slide

  22. OPTIONALS
    NOT IMMUTABLE
    THREAD SAFETY

    View Slide

  23. METHOD INJECTION
    public protocol NSCoding {
    public func encodeWithCoder(aCoder: NSCoder)
    }

    View Slide

  24. AMBIENT CONTEXT
    public class NSURLCache : NSObject {
    public class func setSharedURLCache(cache: NSURLCache)
    public class func sharedURLCache() -> NSURLCache
    }

    View Slide

  25. CROSS-CUTTING CONCERNS
    > logging
    > analitycs
    > time/date
    > etc.

    View Slide

  26. PROS:
    > does not pollute API
    > dependency always available
    CONS:
    > implicit dependency
    > global mutable state

    View Slide

  27. SEPARATION OF CONCERNS
    > what concrete implementations to use
    > configure dependencies
    > manage dependencies' lifetime

    View Slide

  28. WHERE
    DEPENDENCIES ARE
    CREATED?

    View Slide

  29. COMPOSITION ROOT

    View Slide

  30. View Slide

  31. VIPER EXAMPLE
    class AppDependencies {
    init() {
    configureDependencies()
    }
    func configureDependencies() {
    // Root Level Classes
    let coreDataStore = CoreDataStore()
    let clock = DeviceClock()
    let rootWireframe = RootWireframe()
    // List Module Classes
    let listPresenter = ListPresenter()
    let listDataManager = ListDataManager()
    let listInteractor = ListInteractor(dataManager: listDataManager, clock: clock)
    ...
    listInteractor.output = listPresenter
    listPresenter.listInteractor = listInteractor
    listPresenter.listWireframe = listWireframe
    listWireframe.addWireframe = addWireframe
    ...
    }
    }

    View Slide

  32. VIPER EXAMPLE
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    let appDependencies = AppDependencies()
    func application(
    application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
    appDependencies.installRootViewControllerIntoWindow(window!)
    return true
    }
    }

    View Slide

  33. The biggest challange of
    properly implementing DI is
    getting all classes with
    dependencies moved to
    Composition Root
    — Mark Seeman

    View Slide

  34. ANTI-
    PATTERNS

    View Slide

  35. CONTROL FREAK
    class RecipesService {
    let repository: RecipesRepository
    init() {
    self.repository = CoreDataRecipesRepository()
    }
    }

    View Slide

  36. init()

    View Slide

  37. STABLE
    OR
    VOLATILE

    View Slide

  38. VOLATILE DEPENDENCIES
    > dependency requires environment
    configuration (data base, networking, file
    system)
    > nondetermenistic behavior (dates,
    cryptography)
    > expected to be replaced
    > dependency is still in development

    View Slide

  39. VOLATILE
    DEPENDENCIES
    DISABLE LOOSE
    COUPLING

    View Slide

  40. BASTARD INJECTION
    class RecipesService {
    let repository: RecipesRepository
    init(repository: RecipesRepository = CoreDataRecipesRepository()) {
    self.repository = repository
    }
    }

    View Slide

  41. FOREIGN DEFAULT

    View Slide

  42. SERVICE LOCATOR
    let locator = ServiceLocator.sharedInstance
    locator.register( { CoreDataRecipesRepository() },
    forType: RecipesRepository.self)
    class RecipesService {
    let repository: RecipesRepository
    init() {
    let locator = ServiceLocator.sharedInstance
    self.repository = locator.resolve(RecipesRepository.self)
    }
    }

    View Slide

  43. Pros:
    > Extensibility
    > Testability
    > Parallel development
    > Separation of concerns

    View Slide

  44. Cons:
    > Implicit dependencies
    > Hidden complexity
    > Tight coupling
    > Not reusable
    > Less maintainable

    View Slide

  45. > DI enables loose coupling
    > 4 patterns, prefer constructor injection
    > Use local defaults, not foreign
    > Inject volatile dependencies, not stable
    > Avoid anti-patterns

    View Slide

  46. EXPLICIT
    DEPENDENCIES
    COMPOSITION ROOT

    View Slide

  47. SECOND STEP -
    ABSTRACTIONS

    View Slide

  48. DEPENDENCY
    INVERSION
    PRINCIPLE (DIP)

    View Slide

  49. !

    View Slide

  50. !

    View Slide

  51. DI = DI PATTERNS + DIP

    View Slide

  52. Program to an interface,
    not an implementation
    — Design Patterns: Elements of Reusable
    Object-Oriented Software

    View Slide

  53. PROGRAM TO AN
    INTERFACE
    ABSTRACTION

    View Slide

  54. INTERFACES ARE NOT ABSTRACTIONS 1
    1 http://blog.ploeh.dk/2010/12/02/Interfacesarenotabstractions/

    View Slide

  55. DI = DI PATTERNS + DIP

    View Slide

  56. THIRD STEP -
    INVERSION OF
    CONTROL

    View Slide

  57. DI CONTAINERS

    View Slide

  58. TYPHOON
    DIP

    View Slide

  59. TYPHOON
    http://typhoonframework.org
    > a lot of features
    > good docs
    > well maintained
    > continuously improved

    View Slide

  60. public class APIClientAssembly: TyphoonAssembly {
    public dynamic func apiClient() -> AnyObject {
    ...
    }
    public dynamic func session() -> AnyObject {
    ...
    }
    public dynamic func logger() -> AnyObject {
    ...
    }
    }

    View Slide

  61. public dynamic func apiClient() -> AnyObject {
    return TyphoonDefinition.withClass(APIClientImp.self) { definition in
    definition.useInitializer(#selector(APIClientImp.init(session:))) {
    initializer in
    initializer.injectParameterWith(self.session())
    }
    definition.injectProperty("logger", with: self.logger())
    }
    }

    View Slide

  62. public dynamic func session() -> AnyObject {
    return TyphoonDefinition.withClass(NSURLSession.self) { definition in
    definition.useInitializer(#selector(NSURLSession.sharedSession))
    }
    }
    public dynamic func logger() -> AnyObject {
    return TyphoonDefinition.withClass(ConsoleLogger.self) { definition in
    definition.scope = .Singleton
    }
    }

    View Slide

  63. let assembly = APIClientAssembly().activate()
    let apiClient = assembly.apiClient() as! APIClient

    View Slide

  64. TYPHOON + SWIFT !
    > Requires to subclass NSObject and define
    protocols with @objc
    > Methods called during injection should be
    dynamic
    > requires type casting
    > not all features work in Swift
    > too wordy API for Swift

    View Slide

  65. DIP
    https://github.com/AliSoftware/Dip
    > pure Swift api
    > cross-platform
    > type-safe
    > small code base

    View Slide

  66. REGISTER
    let container = DependencyContainer()
    container.register {
    try APIClientImp(
    session: container.resolve()
    ) as APIClient
    }
    .resolveDependencies { container, client in
    client.logger = try container.resolve()
    }
    container.register { NSURLSession.sharedSession() as NetworkSession }
    container.register(.Singleton) { ConsoleLogger() as Logger }

    View Slide

  67. RESOLVE
    let apiClient = try! container.resolve() as APIClient

    View Slide

  68. AUTO-WIRING
    class APIClientImp: APIClient {
    private let _logger = Injected()
    var logger: Logger? { return _logger.value }
    }

    View Slide

  69. AUTO-WIRING
    class APIClientImp: APIClient {
    init(session: NetworkSession) { ... }
    }
    container.register {
    APIClientImp(session: $0) as APIClient
    }

    View Slide

  70. Typhoon Dip
    Constructor, property, method injection ✔ ✔
    Lifecycle management ✔ ✔
    Circular dependencies ✔ ✔
    Runtime arguments ✔ ✔
    Named definitions ✔ ✔
    Storyboards integration ✔ ✔
    -----------------------------------------------------------
    Auto-wiring ✔ ✔
    Thread safety ✘ ✔
    Interception ✔ ✘
    Infrastructure ✔ ✘

    View Slide

  71. WHY SHOULD I BOTHER?
    > easy integration with storyboards
    > manage components lifecycle
    > can simplify configurations
    > allow interception (in Typhoon using
    NSProxy)
    > provides additional features

    View Slide

  72. DI ≠ DI CONTAINER

    View Slide

  73. DEPENDENCY
    INJECTION IS A
    MEANS TO AN END

    View Slide

  74. LINKS
    > "Dependency Injection in .Net" Mark Seeman
    > Mark Seeman's blog
    > objc.io Issue 15: Testing. Dependency
    Injection, by Jon Reid
    > "DIP in the wild"
    > Non-DI code == spaghetti code?

    View Slide

  75. THANK YOU!
    @ILYAPUCHKA
    HTTP://ILYA.PUCHKA.ME

    View Slide