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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Maciej Oczko
June 22, 2015
Programming
1
130
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
56
iOS TDD Workshop (Gdańsk)
maciejoczko
1
82
UICollectionView Basics and Flow Layout
maciejoczko
0
240
UICollectionView Introduction
maciejoczko
0
63
Working With Legacy Code - iOS TDD Workshop
maciejoczko
0
170
Depenedency Injection in iOS
maciejoczko
0
72
Other Decks in Programming
See All in Programming
CSC307 Lecture 07
javiergs
PRO
1
550
Patterns of Patterns
denyspoltorak
0
1.4k
CSC307 Lecture 05
javiergs
PRO
0
500
高速開発のためのコード整理術
sutetotanuki
1
400
それ、本当に安全? ファイルアップロードで見落としがちなセキュリティリスクと対策
penpeen
7
3.9k
CSC307 Lecture 03
javiergs
PRO
1
490
フロントエンド開発の勘所 -複数事業を経験して見えた判断軸の違い-
heimusu
7
2.8k
IFSによる形状設計/デモシーンの魅力 @ 慶應大学SFC
gam0022
1
310
20260127_試行錯誤の結晶を1冊に。著者が解説 先輩データサイエンティストからの指南書 / author's_commentary_ds_instructions_guide
nash_efp
1
980
LLM Observabilityによる 対話型音声AIアプリケーションの安定運用
gekko0114
2
430
Best-Practices-for-Cortex-Analyst-and-AI-Agent
ryotaroikeda
1
110
プロダクトオーナーから見たSOC2 _SOC2ゆるミートアップ#2
kekekenta
0
220
Featured
See All Featured
Designing Powerful Visuals for Engaging Learning
tmiket
0
240
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
330
4 Signs Your Business is Dying
shpigford
187
22k
Ethics towards AI in product and experience design
skipperchong
2
200
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
220
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
122
21k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
750
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
55
Side Projects
sachag
455
43k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.2k
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