Slide 1

Slide 1 text

Dependency Injection on iOS #iOSDI

Slide 2

Slide 2 text

@MaciejOczko #iOSDI

Slide 3

Slide 3 text

Why DIY Frameworks #iOSDI

Slide 4

Slide 4 text

! #iOSDI

Slide 5

Slide 5 text

Tests #iOSDI

Slide 6

Slide 6 text

We all ❤ TDD #iOSDI

Slide 7

Slide 7 text

! #iOSDI

Slide 8

Slide 8 text

! #iOSDI

Slide 9

Slide 9 text

⏰ #iOSDI

Slide 10

Slide 10 text

Code changes ! #iOSDI

Slide 11

Slide 11 text

Mind changes ! #iOSDI

Slide 12

Slide 12 text

Design changes ! #iOSDI

Slide 13

Slide 13 text

DI is the ! pattern while reacting to a change #iOSDI

Slide 14

Slide 14 text

Trust Driven Development? #iOSDI

Slide 15

Slide 15 text

! #iOSDI

Slide 16

Slide 16 text

Why #iOSDI

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

! #iOSDI

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

! #iOSDI

Slide 21

Slide 21 text

Example 3 class MusicAdvisor: NSObject { - (instancetype)init { self = [super init]; if (self) { _albumProvider = [AlbumProvider sharedInstance]; } } } #iOSDI

Slide 22

Slide 22 text

Example 3 continues... class MusicAdvisor: NSObject { - (Soundtrack *)theBestSoundtrackEver { NSArray *allAlbums = [[AlbumProvider sharedInstance] allAlbums]; ... return [Soundtrack withSongs: bestSongsEver]; } } #iOSDI

Slide 23

Slide 23 text

! #iOSDI

Slide 24

Slide 24 text

! #iOSDI

Slide 25

Slide 25 text

Solution class MusicAdvisor: NSObject { - (instancetype)initWithAlbumProvider:(id )albumProvider { self = [super init]; if (self) { _albumProvider = albumProvider; } } } #iOSDI

Slide 26

Slide 26 text

Solution continues... 1 class MusicAdvisor: NSObject { - (instancetype)initWithAlbumProvider:(id )albumProvider { self = [super init]; if (self) { _albumProvider = albumProvider; } } } 2 id albumProvider = [AlbumProvider new]; MusicAdvisor *musicAdvisor = [MusicAdvisor new]; musicAdviser.albumProvider = albumProvider; #iOSDI

Slide 27

Slide 27 text

Initializer approach » Immutability » Clear interface #iOSDI

Slide 28

Slide 28 text

! #iOSDI

Slide 29

Slide 29 text

Benefits » Easier to test classes » Tight-coupling removed » Promotes composition and separation of concepts #iOSDI

Slide 30

Slide 30 text

DIY #iOSDI

Slide 31

Slide 31 text

Strategies 1.Pass objects around 2.Develop application context #iOSDI

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

App context implementation @implementation AppContext ... - (AlbumsDataSource *)albumsDataSourceWithHTTPClient:(id )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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

! #iOSDI

Slide 36

Slide 36 text

Frame works #iOSDI

Slide 37

Slide 37 text

Objection Typhoon #iOSDI

Slide 38

Slide 38 text

Objection » Lightweight » "Annotation" Based Dependency Injection » Bindings » Lazily instantiates dependencies » Eager Singletons » Initializer Support #iOSDI

Slide 39

Slide 39 text

Key concepts » Injector » Module #iOSDI

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Usage JSObjectionInjector *injector = [JSObjection createInjector]; MusicAdvisor *musicAdvisor = [injection getObject:[MusicAdvisor class]]; #iOSDI

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Advanced usage JSObjectionInjector *injector = [JSObjection createInjector:[MyModule new]]; MusicAdvisor *musicAdvisor = [injection getObject:[MusicAdvisor class]]; #iOSDI

Slide 44

Slide 44 text

Try it » Lightweight » Simple » Flexbile #iOSDI

Slide 45

Slide 45 text

... or not? » Lightweight » Not Swift friendly » Property injection approach » Invasive #iOSDI

Slide 46

Slide 46 text

Typhoon » All Objection's features » Life-cycle management » Circular dependecies support » Refactorable » Storyboard integration » Swift support #iOSDI

Slide 47

Slide 47 text

Key concepts » Assembly » Definition #iOSDI

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Usage let assembly = MusicAssembly().activate() let albumProvider = assembly.albumProvider() as! AlbumProvider #iOSDI

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

Usage let assembly = MusicAssembly().activate() let musicAdvisor = assembly.musicAdvisor() as! MusicAdvisor #iOSDI

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Cons » May seem overwhelming » Complex » Learning curve #iOSDI

Slide 55

Slide 55 text

Pros » Configurable » Promotes initializer-injection » Non-invasive » Supports testing » Supports Swift #iOSDI

Slide 56

Slide 56 text

Recap » Dependency injection is powerful pattern » To learn it, build your own injection mechanism » There are frameworks to help you » Objection » Typhoon #iOSDI

Slide 57

Slide 57 text

Thanks! @MaciejOczko #iOSDI