Depin is a library for dependency injection based on Swinject but with some additional wrapping logic.
The overall approach is like this: register dependencies on app launch, then simply use them.
You should register dependencies as soon as possible in your app. Here is an example:
import Depin
import UIKit
@main
enum Main {
@Space(\.dependenciesAssembler)
private static var assembler: Assembler
static func main() {
assembler.apply(assemblies: [
// ...
])
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(AppDelegate.self)
)
}
}
Here you retrieve a Swinject's Assembler
from Space
(it is an analog for SwiftUI's Environment
). After that, you should pass an array of assemblies, each of which contains logic for dependencies' registration. For example:
import Depin
class AppAssembly: Assembly {
func assemble(container: Swinject.Container) {
container.register(ModuleBuilder.self) {
ModuleBuilder()
}
container.registerSynchronized(ApplicationLifeCycleCompositor.self) { r in
ApplicationLifeCycleCompositor(delegates: [
r ~> AnalyticClientCompositor.self,
r ~> LifecycleHandler.self
])
}
}
}
Assembler
from the first example has a property Container
, which is used in assemblies to register dependencies. The same Container
is later used to retrieve them.
You will register dependencies in either of two ways:
- Use this function if the dependency has no dependencies that must be passed in
init
, as withModuleBuilder
case
register<Service>(
_ serviceType: Service.Type,
name: String? = nil,
factory: @escaping () -> Service
)
- Use this function if the dependency has dependencies that must be passed in
init
, as withApplicationLifeCycleCompositor
case
registerSynchronized<Service>(
_ serviceType: Service.Type,
name: String? = nil,
factory: @escaping (Resolver) -> Service
)
To retrieve the dependency, simply use @Injected
property wrapper
class FeedbackFormViewStateFactory {
@Injected private var i18n: I18n
// ...
}
When you register a dependency, you pass a closure that should create an instance of this dependency. Meaning, that if different classes has the same dependency, you will get different instances of it. If you need a singleton behavior, register the dependency like this:
container.register(Test.self) {
Test()
}
.inObjectScope(.container)
What if you need several different singleton-like instances? You should register it with name
. Here is an approach you can use to avoid using string:
- Create a enum with services types an conform it to
ServiceName
import Depin
enum DepinServiceType: String {
case test
}
extension DepinServiceType: ServiceName {
var name: String {
rawValue
}
}
- Create an extension for
Container
in your app
import Depin
extension Container {
@discardableResult
func register<Service>(
_ service: Service.Type,
serviceType: DepinServiceType,
factory: @escaping (Resolver) -> Service
) -> ServiceEntry<Service> {
register(service, name: serviceType.name, factory: factory)
}
@discardableResult
func register<Service>(
_ service: Service.Type,
serviceType: DepinServiceType,
factory: @escaping () -> Service
) -> ServiceEntry<Service> {
register(service, name: serviceType.name, factory: factory)
}
}
- Create an extension for
Injected
in your app
import Depin
extension Injected {
convenience init(_ serviceName: DepinServiceType) {
self.init(serviceName as ServiceName)
}
}
- Use a created registration method
container.register(EventEmitter<Void>.self, serviceType: .test) {
EventEmitter<Void>()
}
.inObjectScope(.container)
- Retrieve a dependency this way
@Injected(.test) private var eventEmitter: EventEmitter<Void>
Use this approach to avoid stating your app when running unit tests
import Depin
import UIKit
@main
enum Main {
@Space(\.dependenciesAssembler)
private static var assembler: Assembler
static func main() {
assembler.apply(assemblies: [
// ...
])
var delegateClassName: String? {
if NSClassFromString("XCTestCase") != nil { // Unit Testing
nil
} else { // App or UI testing
NSStringFromClass(AppDelegate.self)
}
}
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
delegateClassName
)
}
}
Then, in your unit tests code, register mock dependencies for tested services