Skip to content

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

License

Notifications You must be signed in to change notification settings

Frizlab/GlobalConfModule

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Global Conf Module for Swift

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:

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:

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

Using it:

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:

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

Basic Usage (Framework Configuration)

/* 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:

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:

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:

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:

struct ServiceClient {
   @InjectedConf
   var myService: MyService

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

About

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

Topics

Resources

License

Stars

Watchers

Forks

Languages