The Service Locator is a design pattern used to decouple the way objects are obtained from the concrete classes that implement them. This is achieved by centralizing object creation to a single location, known as a service locator.
This tiny project has been inspired by RouterService and Swinject
Modules group related service registrations. They override the build() method to define how services should be registered with the locator.
class MyModule: ServiceLocatorModule {
override func build() {
// singletons
single(ConfigProviderProtocol.self) {
ConfigProvider()
}
// get dependencies with resolve
single(URLFactory.self) {
URLFactory(remoteConfigProvider: self.resolve())
}
// factory
factory(AppLinkFactory.self) {
let settingsManager: SettingsManager = self.resolve()
return AppLinkFactory(descriptionFactory: settingsManager)
}
}
}
Tip: Define an application-wide module that includes other modules.
class AppModule: ServiceLocatorModule {
override func build() {
module { MyModule() }
}
}
lazy var myServiceLocator = startServiceLocator {
AppModule()
}
myServiceLocator.build()
Retrieve dependencies in classes or structs using @Inject. The property wrapper automatically resolves the dependency from the service locator:
class MyCass {
@Inject(myServiceLocator) public var contactSheet: ContactSheet
}
For injecting dependencies within functions or methods, use @Inject before local variable declarations:
func doSomething() {
@Inject(myServiceLocator) var contactSheet: ContactSheet
// Use contactSheet here
}
If you prefer not using property wrappers, directly resolve dependencies using the service locator's resolve method:
let configProvider : ConfigProvider = serviceLocator.resolve()
You can reset the ServiceLocator to its initial state, which can be useful in testing scenarios:
// Reset the entire service locator
resetServiceLocator(myServiceLocator)
// Or, if you have direct access to the ServiceLocator instance:
myServiceLocator.reset()
Create a tailored property wrapper if you have specific needs or want to simplify usage for a particular scope, like plugins in this example:
@propertyWrapper
internal final class PluginInject<T>: Dependency<T> {
public var wrappedValue: T {
resolvedWrappedValue()
}
public init() {
super.init(MyPlugin.shared.myServiceLocator)
}
}
Usage:
class MyClass {
@PluginInject var contactSheet: ContactSheet
}
func doSomething() {
@PluginInject var contactSheet: ContactSheet
}
You can enable logging for the ServiceLocator to help with debugging:
ServiceLocator.enableLogging = true
Add the dependency to your Package.swift
products: [
...
]
dependencies: [
.package(url: "https://github.com/kibotu/ServiceLocator", from: "1.0.2"),
],
targets: [
...
]
- iOS 16.0 or later
- Xcode 15.0 or later
- Swift 5.10 or later
Contributions welcome!
Copyright 2024 Jan Rabe & CHECK24 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.