Swiftpack.co - MwaiBanda/TinyDi as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by MwaiBanda.
MwaiBanda/TinyDi release
A Lightweight Swift Dependency Injection Package
⭐️ 0
🕓 3 weeks ago
iOS macOS watchOS tvOS
.package(url: "https://github.com/MwaiBanda/TinyDi.git", from: "release")

Swift • Dependency Injection • TinyDi

TinyDi is a multi-module dependency injection solution. It uses property wrapper syntax, to make it easy for you to declare & manage your dependencies.

  • Supports Swift functions marked as @TinyModule & @Singleton.
  • Supports Swift variables marked as @Inject & @Binds.
  • Supports SwiftUI and UIKit

Installation

Swift Package Manager File > Swift Packages > Add Package Dependency
Add https://github.com/MwaiBanda/TinyDi.git
branch main

Table of contents

Introduction and references

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.

Setup

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

Extending the 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.

Other ways to provide dependencies

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

@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.

Getting Dependencies

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
...

Testing

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.

GitHub

link
Stars: 0
Last commit: 2 weeks ago
jonrohan Something's broken? Yell at me @ptrpavlik. Praise and feedback (and money) is also welcome.

Release Notes

v1.0.0-beta01
3 weeks ago

Release v1.0.0-beta01

Module Support

  • @TinyModule added
  • Modules added
  • SingleType added

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics