GitHunt
FR

Frizlab/GlobalConfModule

Dependency injection or general configuration framework for Swift (any platforms including Linux and Windows)

= Global Conf Module for Swift
François Lamboley fload@me.com

A fully concurrency-ready generic configuration and dependency injection solution for Swift,
loosely inspired by https://www.avanderlee.com/swift/dependency-injection/.

== Basic Usage (Dependency Injection)

Declaring a service:
[source,swift]

public protocol MyService : Sendable { ... }
public final class DefaultMyService : MyService { ... }

extension ConfKeys {
#declareServiceKey("myService", MyService.self, defaultValue: DefaultMyService())
}
/* You can have as many injected instances of your service as you want by declaring more keys for it. */

Declaring a service which is instantiated via a factory:
[source,swift]

extension ConfKeys {
#declareServiceFactoryKey("myServiceFactory", MyService.self, defaultValue: { DefaultMyService() })
}

Using it:
[source,swift]

struct ServiceClient {
@InjectedConf(.myService) var myService: MyService
@InjectedConf(.myServiceFactory) var myServiceViaFactory: MyService

func myFunction() {
/* The myService variable will always be up-to-date. /
myService.doAmazingStuff(...)
/
Using the default definition set above for myServiceFactory, myServiceViaFactory will always be a new instance. */
myServiceViaFactory.doAmazingStuff(...)
}
}

Changing the injected instance:
[source,swift]

func myAppInit() {
Conf.setRootValue(newService, for: .myService)
}

== Basic Usage (Framework Configuration)

[source,swift]

/* Declare the configuration key. */
extension ConfKeys {
#declareConfKey("myConf", Int.self, defaultValue: 42)
}

/* Use it in your code. */
if Conf[.myConf] == 42 {...}

/* Optionally you can define an internal convenience on Conf for easier access. */
internal extension Conf {
static var myConf: Int {Conf[.myConf]}
}

== Auto-Injected Services

An auto-injected service is a service that can be @InjectedConf w/o specifying a keypath in the property wrapper init. +
This is useful in case there is a “main” instance of your service and specifying a key-path each time the service variable is defined would be redundant.

For these services you can use the AutoInjectable protocol:
[source,swift]

public final class MyService { ... }

extension ConfKeys {
#declareServiceKey("myService"/* or nil if you don’t need the ConfKeys keypath */, MyService.self, "MyServiceKey", defaultValue: DefaultMyService())
}
extension MyService : AutoInjectable {
public typealias AutoInjectionKey = ConfKeys.MyServiceKey
}

And getting the service instance can now be done like so:

[source,swift]

struct ServiceClient {
@InjectedConf()
var myService: MyService
...
}

Note services that are protocols cannot be auto-injectable.

== @MainActor or Other Isolated Services

For @MainActor services you must use the ConfKeyMainActor and AutoInjectableMainActor protocols instead of their non-MainActor equivalents.
The macro have an argument to pass the global actor to use to declare the conf or service:
[source,swift]

extension ConfKeys {
#declareConfKey("myConf", Int.self, on: MainActor.self, defaultValue: 42)
}

For services isolated on other global (or non-global) actors you’ll have to do a little bit more boilerplate.

=== For Global Actors
Basically you will need copy all the *MainActor files and replace the MainActor instances by your own global actor.

=== For Non-Global Actors
This is probably a very advance use-case.
If you need that, ask yourself why and see if you really do.

Like for global actors you will need to copy and adapt the *MainActor files.

I have not tried it, but most likely you will have to remove the global actor annotations and add the isolated parameter you need in the different methods in the files.

== Ordering Service Inits

In the very rare case where you need control on when the service is initialized during the init of your client,
you can use the following pattern:
[source,swift]

struct ServiceClient {
@InjectedConf
var myService: MyService

init() {
/* Do whatever... /
_myService = .init()
/
Do something else... */
}
}

Contributors

MIT License
Created November 11, 2021
Updated March 17, 2026
Frizlab/GlobalConfModule | GitHunt