Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davibe/983aaba09df540c949a886a03b4201d6 to your computer and use it in GitHub Desktop.
Save davibe/983aaba09df540c949a886a03b4201d6 to your computer and use it in GitHub Desktop.
Using protocol composition (intersection types) as simple DI in Swift
// Providers
class Provider1 { }
protocol HasProvider1 { var provider1: Provider1 { get } }
class Provider2 { }
protocol HasProvider2 { var provider2: Provider2 { get } }
// a Provider that depends on the other two needs to be configured
protocol Configurable {
func configure(providerBag: HasProviders)
}
class SubProvider : Configurable {
typealias Dependencies = HasProvider1 & HasProvider2
var deps: Dependencies? = nil
func configure(providerBag: HasProviders) {
self.deps = providerBag
}
func doWork() {
guard let deps = deps else { return } // always check we're configured first
}
}
protocol HasSubProvider { var subProvider: SubProvider { get } }
typealias HasProviders = HasProvider1 & HasProvider2 & HasSubProvider
// some view models depending on Providers
class ViewModel1 : Configurable {
typealias Dependencies = HasProvider1
var deps: Dependencies? = nil
func configure(providerBag: HasProviders) {
self.deps = providerBag
}
}
class ViewModel2 : Configurable {
typealias Dependencies = HasProvider2 & HasSubProvider
var deps: Dependencies? = nil
func configure(providerBag: HasProviders) {
self.deps = providerBag
}
}
// compose a bag for holding all dependencies
struct ProviderBag: HasProvider1 & HasProvider2 & HasSubProvider {
let provider1 = Provider1()
let provider2 = Provider2()
let subProvider = SubProvider()
func bootstrap() {
[provider1, provider2, subProvider]
.compactMap { $0 as? Configurable }
.map { $0.configure(providerBag: self) }
}
func teardown() {
// ...
}
}
class FlowController {
// let invocationParameters = InvocationParameters()
// let localSettings = LocalSettings()
// let Environment = Environment()
// let currentStatus = CurrentStatus()
let providerBag = ProviderBag()
init() { providerBag.bootstrap() }
deinit { providerBag.teardown() }
func atSomePoint() {
let _ = ViewModel2().configure(providerBag: providerBag)
}
}
let flowController = FlowController()
flowController.atSomePoint()
// Very flexible
// - can depend on one or more components
// - can depend on protocols which may help separating concerns
// - can setup providers based on environment/context
// - can support event/reactive based communication between components
// by wiring them up during bootstrap
// Drawbacks
// - While very flexible this architecture ALLOWS having circular
// dependencies between Configurable providers
// - Configurable components always need to unwrap their `deps` basically
// supporting the case they're not configured (disabled)
// - Kotlin does not have intersection types so this is not immediately appliable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment