Container - A lightweight dependency injection framework for Swift

Container

Container is a lightweight dependency injection framework for Swift.

Installation

Container is available through Swift Package Manager.

Swift Package Manager

https://github.com/bartleby/SheetDetentsModifier.git

Basic Usage

First, register a service to a Container, there are two ways to register your dependencies, directly to the Container and through the expansion of DependencyContainer

let container = DependencyContainer.shared
container.apply(Service())

In a real project, it is recommended to use the second approach. You can create a separate file with this extension

struct DependenciesConfigurator {
    static func configure() {
        let container = DependencyContainer.shared
        
        // Setup Modules
        container.apply(AuthorizationAssembly.self)
        container.apply(RegistrationAssembly.self)
        container.apply(OnboardingAssembly.self)
        container.apply(MainAssembly.self)
        container.apply(SettingsAssembly.self)
        
        // Setup Services
        container.apply(AppConfigService() as AppConfigServiceProtocol)
        container.apply(EnvironmentService() as EnvironmentServiceProtocol)
        
        // Appearance
        container.apply(Color.red, label: .redColor)
        
        // Date Formatter
        container.apply(DateFormatterPool() as DateFormatterPoolProtocol)
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        
        // Setup Dependency
        DependenciesConfigurator.configure()
        
        //...

        return true
    }
}

Then get an instance of a service from the container.

let service = container.resolve(Service.self, scope: .unowned)
service.run()

In a real project, it is recommended to use PropertyWrapers

class ViewModel {
    @Container(scope: .strong) var config: AppConfigServiceProtocol
    
    //...
    
    func setup() {
        let token = config.obtain(for: .token)
        //...
    }
    
}

Scope

You can specify the scope for your dependencies, .weak .strong and .unowned

.weak scope is used by default

@Container var service: ServiceProtocol // .weak scope is used

How it’s work

  • weak

Keeps the object in memory while at least one object refers to it

  • strong

An analogue of Singletone, after creating the object, it always remains in memory even if no one refers to it

  • unowned

The object is not stored in memory and each time create a new object

Labels

For the same types, you can specify Label For exemple:

container.apply(Color.red, label: .redColor)
container.apply(Color.green, label: .greenColor)
container.apply(Color.orange, label: .orangeColor)

In order to create your own Label, you need to extension the structure of Containerlabel

extension ContainerLabel {
    static let redColor = ContainerLabel(value: "redColor")
    static let greenColor = ContainerLabel(value: "greenColor")
    static let orangeColor = ContainerLabel(value: "orangeColor")
}

Assembly

Container also support Assembly for assembly your Modules

First, register a Assembly to a Container

extension DependencyContainer {
    func configureAssembly(container: AssemblyApplier) {
        container.apply(MainAssembly.self)
        //...
    }
    
    func configureDependency(container: DependencyApplier) {
        container.apply(AppConfigService() as AppConfigServiceProtocol)
        //...
    }
}

Then implement the MainAssembly class

typealias MainModule = Module<MainModuleInput, MainModuleOutput>

class MainAssembly: Assembly {
    
    @Container(scope: .strong) var config: AppConfigServiceProtocol
    
    func build(coordinator: MainCoordinator) -> MainModule {
        
        // View
        let view = MainViewController()
        
        // Router
        let router = MainRouter(coordinator: coordinator)
        
        // ViewModel
        let viewModel = MainViewModel(router: router, config: config)
        
        // Configure
        viewModel.view = view
        view.output = viewModel
        
        return Module(view: view, input: viewModel, output: viewModel)
    }
}

How to usage

class MainCoordinator: BaseCoordinator {
    
    // MARK: - Dependencies
    @AssemblyContainer var mainAssembly: MainAssembly
    
    override func start() {
        let module = mainAssembly.build(coordinator: self)
        router.setRootModule(module)
    }
}

Example Apps

Coming soon

License

MIT license. See the LICENSE file for details.

GitHub

View Github