Skip to content

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 to ServiceType.

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 SubContainers created later can call this method again to create their own PrintLoggers.

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 Containers and SubContainers. 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.

Comments