Another common Dependency Injection strategy is annotation: adding comments or other metadata to the code which indicates that the following service needs to be resolved by the dependency injection system.
This is commonly done on Android using Dagger 2, and we can now do something similar on iOS.
Resolver now supports resolving properties using the new property wrapper syntax in Swift 5.1.
class BasicInjectedViewController: UIViewController {
@Injected var service: XYZService
}
Just add the Injected property wrapper and your dependencies will be resolved automatically and instantiated immediately, ready and waiting for use.
Note that you still need to register any class or classes that you need to resolve.
Also note that as long as you compile with Swift 5.1, property wrappers work on earlier versions of iOS (11, 12). They're not just limited to iOS 13.
The Injected property wrapper will automatically instantiate objects using the current Resolver root container, exactly as if you'd done var service: XYZService = Resolver.resolve()
. See instructions below on how to specify a different container.
Resolver also has a LazyInjected property wrapper. Unlike using Injected, lazily injected services are not resolved until the code attempts to access the wrapped service.
class NamedInjectedViewController: UIViewController {
@LazyInjected var service: XYZNameService
func load() {
service.load() // service will be resolved at this point in time
}
}
Note that LazyInjected is a mutating property wrapper. As such it can only be used in class instances or in structs when the struct is mutable.
Resolver also has a WeakLazyInjected property wrapper. Like LazyInjected, services are not resolved until the code attempts to access the wrapped service.
class NamedInjectedViewController: UIViewController {
@WeakLazyInjected var service: XYZNameService
func load() {
service.load() // service will be resolved at this point in time
}
}
Note that LazyInjected is a mutating property wrapper. As such it can only be used in class instances or in structs when the struct is mutable.
You can use named service resolution using the name
property wrapper initializer as shown below.
class NamedInjectedViewController: UIViewController {
@Injected(name: "fred") var service: XYZNameService
}
You can also update the name in code and 'on the fly' using @LazyInjected.
class NamedInjectedViewController: UIViewController {
@LazyInjected var service: XYZNameService
var which: Bool
override func viewDidLoad() {
super.viewDidLoad()
$service.name = which ? "fred" : "barney"
}
}
If you go this route just make sure you specify the name before accessing the injected service for the first time.
An annotation is available that supports optional resolving. If the service is not registered, then the value will be nil, otherwise it will be not nil:
class InjectedViewController: UIViewController {
@OptionalInjected var service: XYZService?
func load() {
service?.load()
}
}
Injecting a protocol works with all of the injection property wrappers.
protocol Loader {
func load()
}
class InjectedViewController: UIViewController {
@njected var loader: Loader
func load() {
loader.load()
}
}
Registration of the class providing the protocol instance is performed exactly the same. See Protocols for more.
You can specify and resolve custom containers using Injected. Just define your custom container...
extension Resolver {
static var custom = Resolver()
}
And specify it as part of the Injected property wrapper initializer.
class ContainerInjectedViewController: UIViewController {
@Injected(container: .custom) var service: XYZNameService
}
As with named injection, with LazyInjected you can also dynamically specifiy the desired container.
class NamedInjectedViewController: UIViewController {
@LazyInjected var service: XYZNameService
var which: Bool
override func viewDidLoad() {
super.viewDidLoad()
$service.container = which ? "main" : "test"
}
}
I've written quite a bit more on developing the Injected property wrapper. You can find more information on Injected and property wrappers in my article, Swift 5.1 Takes Dependency Injection to the Next Level on Medium.