Using Services
This guide will show you how to register, configure, and create your own service. In this example we will be assuming two different Logger
implementations.
PrintLogger
: Prints logs.FileLogger
: Saves logs to a file. Already conforms toServiceType
.
Register
Let’s take a look at how we can register our PrintLogger
. First you must conform your type to Service
. The easiest way to do this is simply adding the conformance in an extension.
extension PrintLogger: Service { }
It’s an empty protocol so there should be no missing requirements.
Factory
Now the service can be registered to the Services
struct. This is usually done in configure.swift
.
services.register(Logger.self) { container in
return PrintLogger()
}
By registering the PrintLogger
using a factory (closure) method, we allow the Container
to dynamically create the service once it is needed. Any SubContainer
s created later can call this method again to create their own PrintLogger
s.
Service Type
To make registering a service easier, you can conform it to ServiceType
.
extension PrintLogger: ServiceType {
/// See `ServiceType`.
static var serviceSupports: [Any.Type] {
return [Logger.self]
}
/// See `ServiceType`.
static func makeService(for worker: Container) throws -> PrintLogger {
return PrintLogger()
}
}
Services conforming to ServiceType
can be registered using just the type name. This will automatically conform to Service
as well.
services.register(PrintLogger.self)
Instance
You can also register pre-initialized instances to Services
.
services.register(PrintLogger(), as: Logger.self)
!!! warning
If using reference types (class
) this method will share the same object between all Container
s and SubContainer
s.
Be careful to protect against race conditions.
Configure
If more than one service is registered for a given interface, we will need to choose which service is used.
services.register(PrintLogger.self)
services.register(FileLogger.self)
Assuming the above services are registered, we can use service Config
to pick which one we want.
switch env {
case .production: config.prefer(FileLogger.self, for: Logger.self)
default: config.prefer(PrintLogger.self, for: Logger.self)
}
Here we are using the Environment
to dynamically prefer a service. This is usually done in configure.swift
.
!!! note You can also dynamically register services based on environment instead of using service config. However, service config is required for choosing services that come from the framework or a provider.
Create
After you have registered your services, you can use a Container
to create them.
let logger = try someContainer.make(Logger.self)
logger.log("Hello, world!")
// PrintLogger or FileLogger depending on the container's environment
print(type(of: logger))
!!! tip
Usually the framework will create any required containers for you. You can use BasicContainer
if you want to create one for testing.