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).
- Swift 5.1
- iOS 13.0
DICE is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'DICE'
To run the example project, clone the repo, and run pod install
from the Example directory first.
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
}
}
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"
}
}
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() }
}
}
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
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.
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()
}
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
}
}
- Register
InternalServiceType
container.register(InternalServiceType.self) { _ in
return InternalService()
}
- Resolve
InternalServiceType
from container when registeringInjectableServiceType
and pass it to theInjectableService
container.register(InjectableServiceType.self) { container in
let internalService: InternalServiceType = container.resolve()
return InjectableService(internalService: internalService)
}
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.
container.register(InternalServiceType.self, tag: "dependency1") { _ in
return InternalService(test: "stringInternal")
}
Let's say we want to inject InternalService
with the type InternalServiceType
and tag dependency1
, as per our example above.
- Resolve using
DIContainer.resolve()
let service: InternalServiceType = container.resolve(tag: "dependency1")
- Resolve using property wrappers
@Injected("dependency1") var service: InternalServiceType
Version numbering follows the Semantic versioning approach.
- If you need help, use Stack Overflow (Tag 'dice') or open an issue.
- If you'd like to ask a general question, use Stack Overflow or open an issue.
- If you've found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, submit a pull request.
- If you use and enjoy DICE, please star the project on GitHub.
We’re really happy to accept contributions from the community, that’s the main reason why we open-sourced it!
- Fork it (https://github.com/DICE-Swift/dice/fork)
- Create your feature branch (git checkout -b feature/fooBar)
- Commit your changes (git commit -am 'Add some fooBar')
- Push to the branch (git push origin feature/fooBar)
- Create a new Pull Request
Want to be a first contributor? Please, suggest your improvements by using Contributing section.
DICE is available under the MIT license. See the LICENSE file for more info.