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 in Swift
Search
Ilya Puchka
May 24, 2016
1.6k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Dependency Injection in Swift
Ilya Puchka
May 24, 2016
More Decks by Ilya Puchka
See All by Ilya Puchka
Automation Tests at Babylon
ilyapuchka
0
110
Featured
See All Featured
Building AI with AI
inesmontani
PRO
1
1.1k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.8k
Highjacked: Video Game Concept Design
rkendrick25
PRO
1
410
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
240
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
The Invisible Side of Design
smashingmag
301
52k
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
180
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
590
Building the Perfect Custom Keyboard
takai
2
810
Prompt Engineering for Job Search
mfonobong
0
380
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
Transcript
DEPENDENCY INJECTION (DI) IN SWIFT ILYA PUCHKA (@ILYAPUCHKA) IOS DEVELOPER
@HELLOFRESH
FUNCTIONAL OR OBJECT ORIENTED?
SOLID KISS DRY YAGNI RAP CQS DECORATOR FACADE ABSTRACT FACTORY
STRATEGY ...
DI SOLID
WHAT IS DEPENDENCY INJECTION?
In software engineering, dependency injection is a software design pattern
that implements inversion of control for resolving dependencies. — Wikipedia
“Dependency injection is really just passing in an instance variable.
— James Shore
VOODO MAGIC SLOW FRAMEWORKS ONLY FOR TESTING OVERENGINEERING
> What is Dependency Injection? > How to do it?
> How not to do it?
WHY DEPENDENCY INJECTION?
ABSTRACTIONS EVERYWHERE
LOOSE COUPLING
TEST EXTEND AND REUSE DEVELOP IN PARALLER MAINTAIN
NOT JUST UNIT TESTS BUT LOOSE COUPLING
FIRST STEP - PASSING INSTANCE VARIABLES
SECOND STEP - ? THIRD STEP - ?
PATTERNS CUSTRUCTOR INJECTION PROPERTY INJECTION METHOD INJECTION AMBIENT CONTEXT
CONSTRUCTOR INJECTION class NSPersistentStore : NSObject { init(persistentStoreCoordinator root: NSPersistentStoreCoordinator?,
configurationName name: String?, URL url: NSURL, options: [NSObject: AnyObject]?) var persistentStoreCoordinator: NSPersistentStoreCoordinator? { get } }
EASY TO IMPLEMENT IMMUTABILITY
PROPERTY INJECTION extension UIViewController { weak public var transitioningDelegate: UIViewControllerTransitioningDelegate?
}
LOCAL DEFAULT FOREIGN DEFAULT
OPTIONALS NOT IMMUTABLE THREAD SAFETY
METHOD INJECTION public protocol NSCoding { public func encodeWithCoder(aCoder: NSCoder)
}
AMBIENT CONTEXT public class NSURLCache : NSObject { public class
func setSharedURLCache(cache: NSURLCache) public class func sharedURLCache() -> NSURLCache }
CROSS-CUTTING CONCERNS > logging > analitycs > time/date > etc.
PROS: > does not pollute API > dependency always available
CONS: > implicit dependency > global mutable state
SEPARATION OF CONCERNS > what concrete implementations to use >
configure dependencies > manage dependencies' lifetime
WHERE DEPENDENCIES ARE CREATED?
COMPOSITION ROOT
None
VIPER EXAMPLE class AppDependencies { init() { configureDependencies() } func
configureDependencies() { // Root Level Classes let coreDataStore = CoreDataStore() let clock = DeviceClock() let rootWireframe = RootWireframe() // List Module Classes let listPresenter = ListPresenter() let listDataManager = ListDataManager() let listInteractor = ListInteractor(dataManager: listDataManager, clock: clock) ... listInteractor.output = listPresenter listPresenter.listInteractor = listInteractor listPresenter.listWireframe = listWireframe listWireframe.addWireframe = addWireframe ... } }
VIPER EXAMPLE @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window:
UIWindow? let appDependencies = AppDependencies() func application( application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { appDependencies.installRootViewControllerIntoWindow(window!) return true } }
The biggest challange of properly implementing DI is getting all
classes with dependencies moved to Composition Root — Mark Seeman
ANTI- PATTERNS
CONTROL FREAK class RecipesService { let repository: RecipesRepository init() {
self.repository = CoreDataRecipesRepository() } }
init()
STABLE OR VOLATILE
VOLATILE DEPENDENCIES > dependency requires environment configuration (data base, networking,
file system) > nondetermenistic behavior (dates, cryptography) > expected to be replaced > dependency is still in development
VOLATILE DEPENDENCIES DISABLE LOOSE COUPLING
BASTARD INJECTION class RecipesService { let repository: RecipesRepository init(repository: RecipesRepository
= CoreDataRecipesRepository()) { self.repository = repository } }
FOREIGN DEFAULT
SERVICE LOCATOR let locator = ServiceLocator.sharedInstance locator.register( { CoreDataRecipesRepository() },
forType: RecipesRepository.self) class RecipesService { let repository: RecipesRepository init() { let locator = ServiceLocator.sharedInstance self.repository = locator.resolve(RecipesRepository.self) } }
Pros: > Extensibility > Testability > Parallel development > Separation
of concerns
Cons: > Implicit dependencies > Hidden complexity > Tight coupling
> Not reusable > Less maintainable
> DI enables loose coupling > 4 patterns, prefer constructor
injection > Use local defaults, not foreign > Inject volatile dependencies, not stable > Avoid anti-patterns
EXPLICIT DEPENDENCIES COMPOSITION ROOT
SECOND STEP - ABSTRACTIONS
DEPENDENCY INVERSION PRINCIPLE (DIP)
!
!
DI = DI PATTERNS + DIP
Program to an interface, not an implementation — Design Patterns:
Elements of Reusable Object-Oriented Software
PROGRAM TO AN INTERFACE ABSTRACTION
INTERFACES ARE NOT ABSTRACTIONS 1 1 http://blog.ploeh.dk/2010/12/02/Interfacesarenotabstractions/
DI = DI PATTERNS + DIP
THIRD STEP - INVERSION OF CONTROL
DI CONTAINERS
TYPHOON DIP
TYPHOON http://typhoonframework.org > a lot of features > good docs
> well maintained > continuously improved
public class APIClientAssembly: TyphoonAssembly { public dynamic func apiClient() ->
AnyObject { ... } public dynamic func session() -> AnyObject { ... } public dynamic func logger() -> AnyObject { ... } }
public dynamic func apiClient() -> AnyObject { return TyphoonDefinition.withClass(APIClientImp.self) {
definition in definition.useInitializer(#selector(APIClientImp.init(session:))) { initializer in initializer.injectParameterWith(self.session()) } definition.injectProperty("logger", with: self.logger()) } }
public dynamic func session() -> AnyObject { return TyphoonDefinition.withClass(NSURLSession.self) {
definition in definition.useInitializer(#selector(NSURLSession.sharedSession)) } } public dynamic func logger() -> AnyObject { return TyphoonDefinition.withClass(ConsoleLogger.self) { definition in definition.scope = .Singleton } }
let assembly = APIClientAssembly().activate() let apiClient = assembly.apiClient() as! APIClient
TYPHOON + SWIFT ! > Requires to subclass NSObject and
define protocols with @objc > Methods called during injection should be dynamic > requires type casting > not all features work in Swift > too wordy API for Swift
DIP https://github.com/AliSoftware/Dip > pure Swift api > cross-platform > type-safe
> small code base
REGISTER let container = DependencyContainer() container.register { try APIClientImp( session:
container.resolve() ) as APIClient } .resolveDependencies { container, client in client.logger = try container.resolve() } container.register { NSURLSession.sharedSession() as NetworkSession } container.register(.Singleton) { ConsoleLogger() as Logger }
RESOLVE let apiClient = try! container.resolve() as APIClient
AUTO-WIRING class APIClientImp: APIClient { private let _logger = Injected<Logger>()
var logger: Logger? { return _logger.value } }
AUTO-WIRING class APIClientImp: APIClient { init(session: NetworkSession) { ... }
} container.register { APIClientImp(session: $0) as APIClient }
Typhoon Dip Constructor, property, method injection ✔ ✔ Lifecycle management
✔ ✔ Circular dependencies ✔ ✔ Runtime arguments ✔ ✔ Named definitions ✔ ✔ Storyboards integration ✔ ✔ ----------------------------------------------------------- Auto-wiring ✔ ✔ Thread safety ✘ ✔ Interception ✔ ✘ Infrastructure ✔ ✘
WHY SHOULD I BOTHER? > easy integration with storyboards >
manage components lifecycle > can simplify configurations > allow interception (in Typhoon using NSProxy) > provides additional features
DI ≠ DI CONTAINER
DEPENDENCY INJECTION IS A MEANS TO AN END
LINKS > "Dependency Injection in .Net" Mark Seeman > Mark
Seeman's blog > objc.io Issue 15: Testing. Dependency Injection, by Jon Reid > "DIP in the wild" > Non-DI code == spaghetti code?
THANK YOU! @ILYAPUCHKA HTTP://ILYA.PUCHKA.ME