Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Dependency Injection on iOS
Search
Maciej Oczko
June 22, 2015
Programming
1
120
Dependency Injection on iOS
Given at Mobile Warsaw meet up.
Maciej Oczko
June 22, 2015
Tweet
Share
More Decks by Maciej Oczko
See All by Maciej Oczko
Working with Legacy Code (Łódź)
maciejoczko
0
53
iOS TDD Workshop (Gdańsk)
maciejoczko
1
81
UICollectionView Basics and Flow Layout
maciejoczko
0
240
UICollectionView Introduction
maciejoczko
0
62
Working With Legacy Code - iOS TDD Workshop
maciejoczko
0
170
Depenedency Injection in iOS
maciejoczko
0
69
Other Decks in Programming
See All in Programming
個人軟體時代
ethanhuang13
0
330
「待たせ上手」なスケルトンスクリーン、 そのUXの裏側
teamlab
PRO
0
570
旅行プランAIエージェント開発の裏側
ippo012
2
930
Ruby×iOSアプリ開発 ~共に歩んだエコシステムの物語~
temoki
0
350
速いWebフレームワークを作る
yusukebe
5
1.7k
Namespace and Its Future
tagomoris
6
710
MCPとデザインシステムに立脚したデザインと実装の融合
yukukotani
4
1.5k
奥深くて厄介な「改行」と仲良くなる20分
oguemon
1
560
基礎から学ぶ大画面対応(Learning Large-Screen Support from the Ground Up)
tomoya0x00
0
3.3k
Swift Updates - Learn Languages 2025
koher
2
510
Android端末で実現するオンデバイスLLM 2025
masayukisuda
1
170
Oracle Database Technology Night 92 Database Connection control FAN-AC
oracle4engineer
PRO
1
470
Featured
See All Featured
Reflections from 52 weeks, 52 projects
jeffersonlam
352
21k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
48
9.7k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
33
2.4k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
18
1.1k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.6k
Art, The Web, and Tiny UX
lynnandtonic
303
21k
Raft: Consensus for Rubyists
vanstee
140
7.1k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
34
6k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.1k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
50k
Thoughts on Productivity
jonyablonski
70
4.8k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
8
930
Transcript
Dependency Injection on iOS #iOSDI
@MaciejOczko #iOSDI
Why DIY Frameworks #iOSDI
! #iOSDI
Tests #iOSDI
We all ❤ TDD #iOSDI
! #iOSDI
! #iOSDI
⏰ #iOSDI
Code changes ! #iOSDI
Mind changes ! #iOSDI
Design changes ! #iOSDI
DI is the ! pattern while reacting to a change
#iOSDI
Trust Driven Development? #iOSDI
! #iOSDI
Why #iOSDI
Example 1 class MusicAdvisor: NSObject { - (instancetype)init { self
= [super init]; if (self) { _albumProvider = [[AlbumProvider alloc] initWithMusicService:[MusicService new] artistsProvider:[ArtistsProvider new]]; } } } #iOSDI
! #iOSDI
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
! #iOSDI
Example 3 class MusicAdvisor: NSObject { - (instancetype)init { self
= [super init]; if (self) { _albumProvider = [AlbumProvider sharedInstance]; } } } #iOSDI
Example 3 continues... class MusicAdvisor: NSObject { - (Soundtrack *)theBestSoundtrackEver
{ NSArray<Album *> *allAlbums = [[AlbumProvider sharedInstance] allAlbums]; ... return [Soundtrack withSongs: bestSongsEver]; } } #iOSDI
! #iOSDI
! #iOSDI
Solution class MusicAdvisor: NSObject { - (instancetype)initWithAlbumProvider:(id <AlbumProvider>)albumProvider { self
= [super init]; if (self) { _albumProvider = albumProvider; } } } #iOSDI
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
Initializer approach » Immutability » Clear interface #iOSDI
! #iOSDI
Benefits » Easier to test classes » Tight-coupling removed »
Promotes composition and separation of concepts #iOSDI
DIY #iOSDI
Strategies 1.Pass objects around 2.Develop application context #iOSDI
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
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
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
! #iOSDI
Frame works #iOSDI
Objection Typhoon #iOSDI
Objection » Lightweight » "Annotation" Based Dependency Injection » Bindings
» Lazily instantiates dependencies » Eager Singletons » Initializer Support #iOSDI
Key concepts » Injector » Module #iOSDI
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
Usage JSObjectionInjector *injector = [JSObjection createInjector]; MusicAdvisor *musicAdvisor = [injection
getObject:[MusicAdvisor class]]; #iOSDI
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
Advanced usage JSObjectionInjector *injector = [JSObjection createInjector:[MyModule new]]; MusicAdvisor *musicAdvisor
= [injection getObject:[MusicAdvisor class]]; #iOSDI
Try it » Lightweight » Simple » Flexbile #iOSDI
... or not? » Lightweight » Not Swift friendly »
Property injection approach » Invasive #iOSDI
Typhoon » All Objection's features » Life-cycle management » Circular
dependecies support » Refactorable » Storyboard integration » Swift support #iOSDI
Key concepts » Assembly » Definition #iOSDI
Usage class MusicAssembly: TyphoonAssembly { public dynamic func albumProvider() ->
AnyObject { return TyphoonDefinition.withClass(AlbumProvider.self) } ... } #iOSDI
Usage let assembly = MusicAssembly().activate() let albumProvider = assembly.albumProvider() as!
AlbumProvider #iOSDI
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
Usage let assembly = MusicAssembly().activate() let musicAdvisor = assembly.musicAdvisor() as!
MusicAdvisor #iOSDI
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
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
Cons » May seem overwhelming » Complex » Learning curve
#iOSDI
Pros » Configurable » Promotes initializer-injection » Non-invasive » Supports
testing » Supports Swift #iOSDI
Recap » Dependency injection is powerful pattern » To learn
it, build your own injection mechanism » There are frameworks to help you » Objection » Typhoon #iOSDI
Thanks! @MaciejOczko #iOSDI