You're reading for free via Michael Long's Friend Link. Become a member to access the best of Medium.
Member-only story
Factory: Multiple Module Registration
How to initialize everything before anything is initialized.

When you want to use a dependency injection system like Factory with multiple modules you often run into a “Who’s on first” dilemma.
Let’s say that ModuleP specifies an abstractAccountLoading
protocol.
// ModuleP
public protocol AccountLoading {
func load() -> [Account]
}
Moving on, we have an accounting module, ModuleA, that displays our accounts, but needs one of those loaders to load them.
Finally, we have one more module, let’s call this one ModuleB, that knows how to build loaders of any type that we need.
Note that ModuleA and ModuleB are independent. Neither one knows about the other one, but both have a direct dependency on ModuleP, our master of models and protocols.
This is a classic modular contractural pattern.
But we have an application to build. So how does ModuleA get an instance of an account loader, when it knows nothing about ModuleB?
Well, this is an article about dependency injection, so let’s roll with that.
Resolver
In my earlier dependency injection system, Resolver, that wouldn’t really be a problem. Both modules would import Resolver, and something in ModuleA would simply ask it for an instance of AccountLoading
when needed.
Here’s an example with an Injected property wrapper.
public class ViewModel: ObservableObject {
@Injected var loader: AccountLoading
@Published var accounts: [Account] = []
func load() {
accounts = loader.load()
}
}
And we’re good. Well, I mean, we’re good just as long as someone has registered an instance of AccountLoading
prior to this view model being instantiated and before the Injected property wrapper asks for the instance that it needs.
If they haven’t… well… we’re about to crash. That’s certainly a drawback, but generally speaking forgetting a registration is pretty obvious the first time you try to run and test your code.
But I wanted to do better, so I created Factory.
Compile-Time Safe
Factory promises compile-time safety.
How? Well, Factory accomplishes this by ensuring that a Factory exists to provide the thing you’re looking for. To do that you add the appropriate Factory object to a container… which in turn must be provided with a factory closure which provides an instance of the thing when asked.
In a normal application a Factory registration usually looks something like this.
extension Container {
public static let accountLoader = Factory<AccountLoading> { AccountLoader() }
}
The Container exists. The Factory of type AccountLoading
exists. And the clousure that provides an instance of that type exists. Ergo, we’re guaranteed to be both compile time and run time safe. The code simply won’t compile otherwise.
This type of registration would also work in a modular application if the module both defined our protocol and implemented it. The protocol type and the factory are both public, while the implementation type, AccountLoader
, is defined as internal to the module so it’s safely hidden away.
But in our cross-modular application we can’t do that.
The protocol is defined in ModuleP. The concrete type AccountLoader
exists in ModuleB… but ModuleA doesn’t know about it. It can’t know about.
But the code in ModuleA needs to be able to see the Factory in order to resolve it. And that Factory must have a definition.
Who’s on first?
Optional Registrations
In Factory the general solution to such problems is simply to make our Factory deliver an optional type. So in ModuleA we’d specify this.
extension Container {
public static let accountLoader = Factory<AccountLoading?> { nil }
}
And that’s the instance the code in that module would use to ask for an account loader. Which means that our Factory-based view model code now looks like this.
class ViewModel: ObservableObject {
@Injected(Container.accountLoader) var loader
@Published var accounts: [Account] = []
func load() {
guard let loader else { return }
accounts = loader.load()
}
}
Note the extra guard in the load function ensuring we in fact have the loader we need. More to the point, the use of an optional type ensures that our application is still compile time safe. If, somehow, someone forgets to register something and we end up with nil our application may not behave correctly… but at least it won’t crash.
So how do we make things work? Again, ModuleA has the Factory. But ModuleB contains our networking code that knows how to make an account loader.
The solution is relatively straightforward. Somewhere in the main application we need to write just a little bit of code to wire our two modules together.
import ModuleA
import ModuleB
extension Container {
static func setupModules() {
accountLoader.register { AccountLoader() }
}
}
By registering a new factory closure on accountLoader
we’ve now ensured that when asked it will now return the new service we’ve just registered and not the original nil value.
We’re almost home. Now the only thing left to do is make sure our application calls our function to setup our registrations before everything gets going. Perhaps like this.
@main
struct AccountingApp: App {
init() {
Container.setupModules()
}
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
And we’re done!
But first a side note: I just know that some bright individual will decide to avoid the optional handling and explicitly unwrap the loader, or make the Factory definition itself explicitly unwrapped, but please don’t do any of those things. It pretty much trashes the whole rationale behind Factory in the first place.
More Problems
I mean, we’re done? Right?
Not quite. We still have a couple of potential problems.
This first one is that we have to remember to call our setup function. Problematic, yes, but on it’s own it’s not a deal breaker.
But the second one is a little more insidious. Consider this.
@main
struct AccountingApp: App {
let viewModel = ModuleA.ViewModel()
init() {
Container.setupModules()
}
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
.environmentObject(ViewModel)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
Let’s say that we need to put ModuleA’s view model into the environment, and to do that, we simply create one as a property.
And there’s the problem.
You see, when the system attempts to create an instance of AccountingApp
the properties on that struct will be initialized first, prior to its initializer firing. That means the ViewModel
will be created and its Injected property wrapper will ask its Factory for an account loader… all before our setup function will be called.
So we end up with nothing. Literally.
This example is a bit contrived, but in multi-module environments it actually occurs a lot more often than one might think.
AutoRegistering
Resolver solved this problem by automatically calling a setup function prior to the very first resolution occurring, and it just so happens that I just added a version of that same exact methodology to Factory.
Here’s our revised setup function. Note that the name of the function has changed to registerAllServices
and that our extension now conforms to AutoRegistering
.
import ModuleA
import ModuleB
extension Container: AutoRegistering {
static func registerAllServices {
accountLoader.register { AccountLoader() }
}
}
Which lets us eliminate the need for our initializer altogether.
@main
struct AccountingApp: App {
let viewModel = ModuleA.ViewModel()
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
.environmentObject(ViewModel)
.navigationViewStyle(StackNavigationViewStyle())
}
}
}
Factory will now automatically call registerAllServices
for us, once (and only once) prior to the very first Factory resolution.
Internals
If you’re a geek (and you probably are if you’re still reading this far), the internals of how this is accomplished is rather interesting.
The protocol is straightforward enough.
public protocol AutoRegistering {
static func registerAllServices()
}
And we saw how to use it earlier. But how do we check to see if a Container class conforms to that protocol? And how do we ensure that our function is only called once?
extension Container {
fileprivate static var autoRegistrationCheck: Void = {
(Container.self as? AutoRegistering.Type)?.registerAllServices()
}()
}
Let’s tackle the one-time only problem first. In the old days we would have used dispatch_once
, but Swift tells us that function is deprecated and that we should consider using a static variable instead.
So we did, creating autoRegistrationCheck
. And, as promised, Swift itself ensures that that our static variable initializer is called once and only once.
Within the function, the(Container.self as? AutoRegistering.Type)
code looks a little weird, but basically it just checks to see if someone has extended the Container
type to conform to theAutoRegistering
type.
If the casted type isn’t nil we call registerAllServices
on the class type. Simple.
The only thing remaining in Factory is to call it prior to the first resolution.
func resolve(_ params: P) -> T {
let _ = Container.autoRegistrationCheck
// remaining code
}
If Container conforms to AutoRegistering
thenregisterAllServices
is called as a side effect and the returned Void
is discarded. On subsequent invocations, we ask the static variable for a Void
which we’re going to discard anyway… which results in very little code executed. (The SIL code on this is interesting.)
Resolver’s internal checks to do the same thing were a lot less cleaner than this, but you’re welcome to examine that code if you please.
Completion Block
So to summarize the situation…
- We have a public
AccountLoading
protocol in ModuleP, - We also have a public
Container
extension in ModuleP. - We have a public
AccountLoader
class visible in ModuleB. - And we have the app, who can “see” both P and B and wire things together.
This provides ModuleA, which can only see ModuleP, a way to get what it needs to do the job without, in fact, knowing about the existence of ModuleB.
That’s it. Some multiple-module registration and resolution tactics to use with Factory, and a brief look behind the scenes as to how the magic occurs.
You know the drill. Ask your questions and make your comments in the section below, and hold down the like button for awhile if you want to see more.
Seriously. Medium has changed their promotion and payout algorithms, so clapping and leaving comments makes a HUGE difference.
Til next time.
This article is part of The Swift Dependency Injection Series.