TinyDi is a multi-module dependency injection solution. It uses property wrapper syntax, to make it easy for you to declare & manage your dependencies.
@TinyModule
& @Singleton
.@Inject
& @Binds
.Swift Package Manager
File > Swift Packages > Add Package Dependency
Add https://github.com/MwaiBanda/TinyDi.git
branch main
Here is a small example.
extension DependencyRegistry {
func inject() {
TDi.inject(context: { reslover in
provideGreeter()
})
}
@Singleton
private func provideGreeter() {
Greeter(greeting: "Hello, Tiny World!")
}
}
func main() {
// MARK: Call initialisation function
DependencyRegistry.shared.inject()
// MARK: Dependency Retrival
@Inject var greeter: Greeter
print(greeter.greeting)
}
You can get the full code here.
TinyDi alllows for easy separations of cocerns, the library follows the dependency rule participularily with modules, dependencies within
a module can not depend on each other within the same module. To start, you have to extend the Dependenpce Registry
class
Dependenpce Registry
You can place up the extension function app file
// MARK: Dependency Insertion
// Step 1 :: Extend DependencyRegistry
extension DependencyRegistry {
// Step 2 :: Create an initialisation function
func inject() {
TDi.inject(context: { reslover in
// Step 3.b :: reference provider function
provideGreeter()
})
}
// Step 3.a :: Create a provider function for a dependency
@Singleton
private func provideGreeter() {
Greeter(greeting: "Hello, Tiny World!")
}
}
func main() {
// Step 4 :: Call initialisation function
DependencyRegistry.shared.inject()
// MARK: Dependency Retrival
@Inject var greeter: Greeter
print(greeter.greeting)
}
You can get the full code here.
TinyDi offers other ways to for you to provide your dependencies. Dependencies can be provide with
the following property-wrappers & @TinyModule
& @Binds
:
@TinyModule:
Functions prefixed with @TinyModule
allow you to build modules of dependencies within them.
Modules allow easier separation of different dependencies.
@TinyModule
func singletonModule(){
Module(
Single(Auth.auth(), named: "Auth"), // Optional naming of dependencies within a module
Single(DatabaseDriverFactory())
)
}
You can get the full code here.
In cases were you need dependencies from another module, make (resolver: TinyDi)
the function
signature. With reference to the resolver you can call resolver.resolve()
in place of the required
dependency. Additionally, when providing a dependency you can explicitly specify it's type, this is useful
for dependency inversion
@TinyModule
func controllerModule(resolver: TinyDi) {
Module(
Single<TransactionController>(TransactionControllerImpl(driverFactory: resolver.resolve())),
Single<AuthController>(AuthControllerImpl())
)
}
You can get the full code here.
After creating your modules, add them to the Dependency Registry
extension DependencyRegistry {
func inject() {
TDi.inject { resolver in
singletonModule()
controllerModule(resolver: resolver)
}
}
}
You can get the full code here.
@Binds:
Variables prefixed with the @Binds
allow you provide dependencies in them. A typical usecase would be dependency inversion
extension DependencyRegistry {
func inject() {
TDi.inject { resolver in
singletonModule()
controllerModule(resolver: resolver)
@Binds var authController: AuthController = {
AuthControllerImpl()
}()
}
}
}
extension DependencyRegistry {
func inject() {
TDi.inject { resolver in
singletonModule()
controllerModule(resolver: resolver)
@Binds var authId: String = {
Auth.auth().currentUser?.id ?? ""
}()
}
}
}
You can get the full code here.
In cases, were you want to bind dependencies of the same type. Use @Binds(named: "SomeKey")
to differentiate
one from the other.
@Binds(named: String
):
extension DependencyRegistry {
func inject() {
TDi.inject { resolver in
singletonModule()
controllerModule(resolver: resolver)
@Binds(named: "APIKey") var apiKey: String = {
"XXX.xxx.xx.00"
}()
}
}
}
You can get the full code here.
Variables prefixed with the @Inject
allow you retrieve dependencies from them by
declaring an explicity type variable
@Inject:
class TransactionViewModel: ObservableObject {
@Inject private var controller: TransactionController
...
Alternatively, you can retrive specific named dependencies
@Inject(named: String
):
class AuthViewModel: ObservableObject {
@Inject(named: "Auth") private var auth: Auth
...
TinyDi can also be used to provide your test dependencies
Step 1:
Create your test modules
import Foundation
import XCTest
@testable import TinyDi
@TinyModule
func authModule() {
Module(
Single<AuthProviding>(Auth())
)
}
@TinyModule
func dataModule(resolver: TinyDi) {
Module(
Single<DataProviding>(Data(auth: resolver.resolve()))
)
}
You can get the full code here.
Step 2:
Extend DependencyRegistry
& create an initialisation function then add your modules
import XCTest
@testable import TinyDi
import Foundation
extension DependencyRegistry {
func injectTest() {
TDi.inject { resolver in
authModule()
dataModule(resolver: resolver)
}
}
}
You can get the full code here.
Step 3:
Create a base test class
import Foundation
import XCTest
@testable import TinyDi
class BaseXCTestCase: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
DependencyRegistry.shared.clear {
DependencyRegistry.shared.injectTest()
}
}
override func tearDownWithError() throws {
try super.tearDownWithError()
}
}
You can get the full code here.
Use dependencies
final class TinyDiTests: BaseXCTestCase {
@Inject private var data: DataProviding
override func setUpWithError() throws {
try super.setUpWithError()
}
override func tearDownWithError() throws {
try super.tearDownWithError()
_data.release()
}
...
You can get the full code here.
link |
Stars: 1 |
Last commit: 13 weeks ago |
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics