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

Dependency Injection in Swift

Avatar for Ilya Puchka Ilya Puchka
May 24, 2016
1.5k

Dependency Injection in Swift

Avatar for Ilya Puchka

Ilya Puchka

May 24, 2016
Tweet

Transcript

  1. In software engineering, dependency injection is a software design pattern

    that implements inversion of control for resolving dependencies. — Wikipedia
  2. CONSTRUCTOR INJECTION class NSPersistentStore : NSObject { init(persistentStoreCoordinator root: NSPersistentStoreCoordinator?,

    configurationName name: String?, URL url: NSURL, options: [NSObject: AnyObject]?) var persistentStoreCoordinator: NSPersistentStoreCoordinator? { get } }
  3. AMBIENT CONTEXT public class NSURLCache : NSObject { public class

    func setSharedURLCache(cache: NSURLCache) public class func sharedURLCache() -> NSURLCache }
  4. PROS: > does not pollute API > dependency always available

    CONS: > implicit dependency > global mutable state
  5. SEPARATION OF CONCERNS > what concrete implementations to use >

    configure dependencies > manage dependencies' lifetime
  6. 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 ... } }
  7. 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 } }
  8. The biggest challange of properly implementing DI is getting all

    classes with dependencies moved to Composition Root — Mark Seeman
  9. VOLATILE DEPENDENCIES > dependency requires environment configuration (data base, networking,

    file system) > nondetermenistic behavior (dates, cryptography) > expected to be replaced > dependency is still in development
  10. 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) } }
  11. > DI enables loose coupling > 4 patterns, prefer constructor

    injection > Use local defaults, not foreign > Inject volatile dependencies, not stable > Avoid anti-patterns
  12. !

  13. !

  14. Program to an interface, not an implementation — Design Patterns:

    Elements of Reusable Object-Oriented Software
  15. public class APIClientAssembly: TyphoonAssembly { public dynamic func apiClient() ->

    AnyObject { ... } public dynamic func session() -> AnyObject { ... } public dynamic func logger() -> AnyObject { ... } }
  16. 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()) } }
  17. 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 } }
  18. 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
  19. 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 }
  20. AUTO-WIRING class APIClientImp: APIClient { init(session: NetworkSession) { ... }

    } container.register { APIClientImp(session: $0) as APIClient }
  21. Typhoon Dip Constructor, property, method injection ✔ ✔ Lifecycle management

    ✔ ✔ Circular dependencies ✔ ✔ Runtime arguments ✔ ✔ Named definitions ✔ ✔ Storyboards integration ✔ ✔ ----------------------------------------------------------- Auto-wiring ✔ ✔ Thread safety ✘ ✔ Interception ✔ ✘ Infrastructure ✔ ✘
  22. WHY SHOULD I BOTHER? > easy integration with storyboards >

    manage components lifecycle > can simplify configurations > allow interception (in Typhoon using NSProxy) > provides additional features
  23. 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?