Skip to content

Property based dependency injection framework for Swift.

License

Notifications You must be signed in to change notification settings

DICE-Swift/dice

Repository files navigation

DICE

CI Status codecov Version License Platform

DICE is a lightweight Swift framework that provides property based dependency injection for Swift and SwiftUI projects. DICE also provides service locator pattern with the help of containers. Easily inject your dependencies through property wrappers or through DI container. DICE supports different scopes (singleton, lazy weak, lazy prototype, lazy object graph).

Requirements

  • Swift 5.1
  • iOS 13.0

Installation

DICE is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'DICE'

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Usage

Inject through DI container

1. Add import

import DICE

2. Declare your container

let container = DIContainer()

3. Register your instances

container.register(DummyServiceType.self) { _ in
    return DummyService()
}

E.g. DummyServiceType is just a protocol and DummyService is an implementation.

protocol DummyServiceType {
    func test()
}

class DummyService: DummyServiceType {
    func test() {
        Swift.print("DummyService")
    }
}

4. Pass container to DICE

DICE.use(container)

5. Resolve instance

Using DIContainer

import UIKit
import DICE

class ViewController: UIViewController {
    let container = DIContainer()
    // In real case you'll need to pass container in ViewController or another class and all the dependencies should have been already registered prior to using container
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        container.register(DummyServiceType.self, scope: .single) { _ in
            return DummyService()
        }
        
        DICE.use(container)
        
        let service: DummyServiceType = container.resolve()
        service.test()
        
        // It should print "DummyService" in Xcode console
        // If you get error here, so check previous steps or open an issue
    }
}

Using @Injected Property Wrapper

import UIKit
import DICE

class ViewController: UIViewController {
    @Injected var dummyService: DummyServiceType
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        dummyService.test()
        // It should print "DummyService" in Xcode console
        // If you get error here, so check previous steps or open an issue
    }
}

Inject in SwiftUI

EnvironmentObservableInjected

Dynamic view property wrapper that subscribes to a ObservableObject automatically invalidating the view when it changes.

struct ContentView: View {
    @EnvironmentObservableInjected var viewModel: ContentViewModel

    var body: some View {
        HStack {
            Text(viewModel.title)
        }.onAppear { self.viewModel.startUpdatingTitle() }
    }
}

...

class ContentViewModel: ObservableObject {
    @Published private(set) var title: String = ""
    
    func startUpdatingTitle() {
        self.title = "Test"
    }
}

EnvironmentInjected

Property wrapper that inject object from environment container. Read only object. Typically used for non-mutating objects.

struct ContentView: View {
    @EnvironmentInjected var service: WebService

    var body: some View {
        HStack {
            Text("Waiting...")
        }.onAppear { self.service.auth() }
    }
}

Advanced usage

Scopes

Default scope

Default scope is DIScope.objectGraph. All registered objects will use default scope if no scope set when registering. It can be changed by setting DICE.Defaults.scope, for example:

DICE.Defaults.scope = .single

Supported scopes

  • DIScope.single

Dependency is created per container as single instance. Recommended to use for singletones that should be instantiated as soon as injected in the DIContainer.

  • DIScope.weak

Dependency is lazily created one per container, but destroys when dependency object will deinit. Object will be instantiated only after first call.

  • DIScope.prototype

Dependency instance is lazily created each time. Object will be instantiated only after first call.

  • DIScope.objectGraph

Dependency instance is lazily created one per object graph. Object will be instantiated only after first call.

Set scope

You can specify scopy by using optional scope parameter when registering an object. If no scope passed then default scope DIScope.Defaults.scope is used.

container.register(InjectableServiceType.self, scope: .objectGraph) { _ in
    return InjectableService()
}

Using container resolver when injecting

Let's say InjectableService requires to recieve InternalServiceType in the initializer as a dependency.

class InjectableService: InjectableServiceType {
    let internalService: InternalServiceType
    
    init(internalService: InternalServiceType) {
        self.internalService = internalService
    }
}
  1. Register InternalServiceType
container.register(InternalServiceType.self) { _ in
    return InternalService()
}
  1. Resolve InternalServiceType from container when registering InjectableServiceType and pass it to the InjectableService
container.register(InjectableServiceType.self) { container in
    let internalService: InternalServiceType = container.resolve()
    return InjectableService(internalService: internalService)
}

Tags

When you registering dependencies you could assign a tag for an object. When you resolve dependencies either using DIContainer.resolve() or property wrappeprs Injected, EnvironmentObservableInjected, EnvironmentInjected use a tag to inject an object that matches the tag you passed.

Register dependency with a tag

container.register(InternalServiceType.self, tag: "dependency1") { _ in
    return InternalService(test: "stringInternal")
}

Resolve dependency with a tag

Let's say we want to inject InternalService with the type InternalServiceType and tag dependency1, as per our example above.

  1. Resolve using DIContainer.resolve()
let service: InternalServiceType = container.resolve(tag: "dependency1")
  1. Resolve using property wrappers
@Injected("dependency1") var service: InternalServiceType

Versioning

Version numbering follows the Semantic versioning approach.

Communication and Help

Contributing

We’re really happy to accept contributions from the community, that’s the main reason why we open-sourced it!

  1. Fork it (https://github.com/DICE-Swift/dice/fork)
  2. Create your feature branch (git checkout -b feature/fooBar)
  3. Commit your changes (git commit -am 'Add some fooBar')
  4. Push to the branch (git push origin feature/fooBar)
  5. Create a new Pull Request

Contributors

Maintainers

Contributors

Want to be a first contributor? Please, suggest your improvements by using Contributing section.

License

DICE is available under the MIT license. See the LICENSE file for more info.