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
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Maciej Oczko
June 22, 2015
Programming
130
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Dependency Injection on iOS
Given at Mobile Warsaw meet up.
Maciej Oczko
June 22, 2015
More Decks by Maciej Oczko
See All by Maciej Oczko
Working with Legacy Code (Łódź)
maciejoczko
0
59
iOS TDD Workshop (Gdańsk)
maciejoczko
1
85
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
73
Other Decks in Programming
See All in Programming
「寝てても仕事が進む」Claude Codeで組む第二の脳
tomoyafujita2016
0
370
「つくるAI」だけではバグは見つからない ~テストに必要な「見つけるAI」を分離させる戦略~
mfunaki
0
200
MySQLとPostgreSQLって何が違うの?
akagami
0
150
Dockerfile CMD for Node.js
grazie1999
0
140
業務時間外もAIに働いてもらう話
colorful12
2
8.9k
Press start. Python's next generation.
willingc
PRO
3
270
ドリフトを絶対に許さない(?)CDK運用 / CDK Ops with Zero Tolerance for Drifts (?)
akihisaikeda
1
250
書籍「プロフェッショナルAI駆動開発」紹介スライド
juntaromatsumoto
0
800
仕様駆動開発の消費期限
watany
20
9.1k
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
460
属人化した知識を、 AIが辿れる地図にする
pkshadeck
PRO
1
220
React本体のコードリーディング
high_g_engineer
1
160
Featured
See All Featured
Raft: Consensus for Rubyists
vanstee
141
7.7k
Testing 201, or: Great Expectations
jmmastey
46
8.3k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
390
Optimising Largest Contentful Paint
csswizardry
37
3.9k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
A better future with KSS
kneath
240
18k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.8k
The Curse of the Amulet
leimatthew05
2
14k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.7k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
Building Flexible Design Systems
yeseniaperezcruz
330
41k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
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