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

Dependency Injection on iOS

Dependency Injection on iOS

Given at Mobile Warsaw meet up.

Avatar for Maciej Oczko

Maciej Oczko

June 22, 2015
Tweet

More Decks by Maciej Oczko

Other Decks in Programming

Transcript

  1. Example 1 class MusicAdvisor: NSObject { - (instancetype)init { self

    = [super init]; if (self) { _albumProvider = [[AlbumProvider alloc] initWithMusicService:[MusicService new] artistsProvider:[ArtistsProvider new]]; } } } #iOSDI
  2. Example 2 class MusicAdvisor: NSObject { - (instancetype)init { self

    = [super init]; if (self) { self.musicService = [MusicService new]; self.artistsProvider = [ArtistsProvider new]; _albumProvider = [[AlbumProvider alloc] initWithMusicService:self.musicService artistsProvider:self.artistsProvider]; } } } #iOSDI
  3. Example 3 class MusicAdvisor: NSObject { - (instancetype)init { self

    = [super init]; if (self) { _albumProvider = [AlbumProvider sharedInstance]; } } } #iOSDI
  4. Example 3 continues... class MusicAdvisor: NSObject { - (Soundtrack *)theBestSoundtrackEver

    { NSArray<Album *> *allAlbums = [[AlbumProvider sharedInstance] allAlbums]; ... return [Soundtrack withSongs: bestSongsEver]; } } #iOSDI
  5. Solution continues... 1 class MusicAdvisor: NSObject { - (instancetype)initWithAlbumProvider:(id <AlbumProvider>)albumProvider

    { self = [super init]; if (self) { _albumProvider = albumProvider; } } } 2 id <AlbumProvider> albumProvider = [AlbumProvider new]; MusicAdvisor *musicAdvisor = [MusicAdvisor new]; musicAdviser.albumProvider = albumProvider; #iOSDI
  6. Benefits » Easier to test classes » Tight-coupling removed »

    Promotes composition and separation of concepts #iOSDI
  7. App context usage @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

    { self.window.rootViewController = [self.appContext rootViewController]; [self.window makeKeyAndVisible]; return YES; } ... @end #iOSDI
  8. App context implementation @implementation AppContext ... - (AlbumsDataSource *)albumsDataSourceWithHTTPClient:(id <HTTPClient>)httpClient

    { return [[AlbumsDataSource alloc] initWithHTTPClient:httpClient]; } - (AlbumsViewController *)rootViewController { AlbumsDataSource *dataSource = [self albumsDataSourceWithHTTPClient:[self httpClient]]; AlbumsViewController *albumsViewController = [[AlbumsViewController alloc] initWithDataSource:dataSource]; albumsViewController.appContext = self; return albumsViewController; } @end #iOSDI
  9. App context usage @implementation AlbumsViewController ... - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath

    *)indexPath { ... AlbumDetailsViewController *advc = [self.appContext albumDetailsViewContorllerWithAlbum:album]; [self presentViewController:advc animated:YES completion:nil]; } @end #iOSDI
  10. Objection » Lightweight » "Annotation" Based Dependency Injection » Bindings

    » Lazily instantiates dependencies » Eager Singletons » Initializer Support #iOSDI
  11. Usage @interface MusicAdvisor @property(nonatomic, strong, readonly) AlbumProvider *albumProvider; @end @implementation

    MusicAdvisor objection_requires_sel(@selector(albumProvider)) - (void)awakeFromObjection { // Initialize with injected properties } @end #iOSDI
  12. Advanced usage @interface MyModule : JSObjectionModule @end @implementation MyModule -

    (void)configure { [self bindClass:[AlbumProvider class] toProtocol:@protocol(AlbumProvider)]; [self bindProvider:[MusicAdvisorProvider new] toClass:[MusicAdvisor class]]; [self bindBlock:^(JSObjectionInjector *context) { // Manual creation return soundTrack; } toClass:[Soundtrack class]]; [self bindClass:[MusicAdvisor class] inScope:JSObjectionScopeSingleton]; } @end #iOSDI
  13. ... or not? » Lightweight » Not Swift friendly »

    Property injection approach » Invasive #iOSDI
  14. Typhoon » All Objection's features » Life-cycle management » Circular

    dependecies support » Refactorable » Storyboard integration » Swift support #iOSDI
  15. Usage class MusicAssembly: TyphoonAssembly { public dynamic func albumProvider() ->

    AnyObject { return TyphoonDefinition.withClass(AlbumProvider.self) } ... } #iOSDI
  16. Usage class MusicAssembly: TyphoonAssembly { public dynamic func albumProvider() ->

    AnyObject { return TyphoonDefinition.withClass(AlbumProvider.self) } public dynamic func musicAdvisor() -> AnyObject { return TyphoonDefinition.withClass(MusicAdvisor.self) { definition in definition.useInitializer("initWithAlbumProvider:") { initializer in initializer.injectParameterWith(self.albumProvider()) } definition.injectProperty("soundTrackComposer", self.soundtrackComposer()) definition.injectProperty("assembly") // definition.scope = TyphoonScope.Singleton } } } #iOSDI
  17. Advanced usage public class MusicAssembly: TyphoonAssembly { public var themesAssembly:

    ThemesAssembly! public dynamic func musicAdvisor(albumProvier: AlbumProvider) -> AnyObject { return TyphoonDefinition.withClass(MusicAdvisor.self) { definition in definition.useInitializer("initWithAlbumProvider:themesComposer:") { initializer in initializer.injectParameterWith(albumProvider) initializer.injectParameterWith(self.themesAssembly.defaultComposer) } } } public dynamic func appDelegate() -> AnyObject { return TyphoonDefinition.withClass(AppDelegate.self) { (definition) in definition.injectProperty("musicAdvisor", with: self.musicAdvisor()) definition.injectProperty("rootViewController", with: RootViewController()) definition.injectProperty("assembly") } } ... } #iOSDI
  18. Advanced usage @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var musicAdvisor:

    MusicAdvisor? var rootViewController: RootViewController? var assemby: MusicAssembly? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Actiation via Info.plist - InitialTyphoonAssemblies self.window?.rootViewController = self.rootViewController self.musicAdvisor?.adviseMe() { [unowned self] music in let musicPlayer = self.assembly.musicPlayer(music: music) ... } return true } } #iOSDI
  19. Recap » Dependency injection is powerful pattern » To learn

    it, build your own injection mechanism » There are frameworks to help you » Objection » Typhoon #iOSDI