SkillAgentSearch skills...

Factory

A modern approach to Container-Based Dependency Injection for Swift and SwiftUI.

Install / Use

/learn @hmlongco/Factory
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

A modern approach to Container-Based Dependency Injection for Swift and SwiftUI.

Factory Version 2.5.3

Factory is strongly influenced by SwiftUI, and in my opinion is highly suited for that environment. Factory is...

  • Adaptable: Factory doesn't tie you down to a single dependency injection strategy or technique.
  • Powerful: Factory supports containers, scopes, passed parameters, contexts, decorators, unit tests, SwiftUI Previews, and much, much more.
  • Performant: Little to no setup time is needed for the vast majority of your services, resolutions are extremely fast, and no compile-time scripts or build phases are needed.
  • Safe: Factory is compile-time safe; a factory for a given type must exist or the code simply will not compile.
  • Concise: Defining a registration usually takes just a single line of code. Same for resolution.
  • Flexible: Working with UIKIt or SwiftUI? iOS or macOS? Using MVVM? MVP? Clean? VIPER? No problem. Factory works with all of these and more.
  • Documented: Factory has extensive DocC documentation and examples covering its classes, methods, and use cases.
  • Lightweight: With all of that Factory is slim and trim, under 1,000 lines of executable code.
  • Tested: Unit tests with 100% code coverage helps ensure correct operation of registrations, resolutions, and scopes.
  • Testable: Factory ensures your application's views and services are easily previewable and testable.
  • Free: Factory is free and open source under the MIT License.

Sound too good to be true? Let's take a look.


But before we do, I want to express my thanks to Mercedes-Benz, Süddeutsche Zeitung, and everyone else who's sponsored my open source work! You folks help make this possible.


A Simple Factory

Most container-based dependency injection systems require you to define in some way that a given service type is available for injection, and many require some sort of factory or mechanism that will provide a new instance of the service when needed.

Factory is no exception. Here's a simple dependency registration that returns a service that conforms to MyServiceType.

extension Container {
    var myService: Factory<MyServiceType> { 
        Factory(self) { MyService() }
    }
}

Unlike frameworks that require registering every single type up front, or SwiftUI, where defining a new environment variable requires creating a new EnvironmentKey and adding additional getters and setters, here we simply add a new Factory computed variable to the default container. When it's called our Factory is created, its closure is evaluated, and we get an instance of our dependency when we need it.

Injecting an instance of our service is equally straightforward. Here's just one of the many ways Factory can be used.

class ContentViewModel: ObservableObject {
    @Injected(\.myService) private var myService
    ...
}

This particular view model uses one of Factory's @Injected property wrappers to request the desired dependency. Similar to @Environment in SwiftUI, we provide the property wrapper with a keyPath to a factory of the desired type and it resolves that type the moment ContentViewModel is created.

And that's the core mechanism. In order to use the property wrapper you must define a factory within the specified container. That factory must return the desired type when asked. Fail to do either one and the code will simply not compile. As such, Factory is compile-time safe.

By the way, if you're concerned about building Factory's on the fly, don't be. Like SwiftUI Views, Factory structs and modifiers are lightweight and transitory value types. They're created inside computed variables only when they're needed and then immediately discarded once their purpose has been served.

For more examples of Factory definitions that define scopes, use constructor injection, and do parameter passing, see the Registrations page.

Other Factory Resolution Methods

Earlier we demonstrated how to use the Injected property wrapper. But it's also possible to bypass the property wrapper and talk to the factory yourself.

You can use the shared container:

class ContentViewModel: ObservableObject {
    private let myService = Container.shared.myService()
    private let eventLogger = Container.shared.eventLogger()
    ...
}

Just call the desired factory as a function and you'll get an instance of its managed dependency. It's that simple.

If you're into container-based dependency injection, note that you can also pass an instance of a container to a view model and obtain an instance of your service directly from that container.

class ContentViewModel: ObservableObject {
    let service: MyServiceType
    init(container: Container) {
        service = container.service()
    }
}

Or if you want to use a Composition Root structure, just use the container to provide the required dependencies to a constructor.

extension Container {
    var myRepository: Factory<MyRepositoryType> {
        Factory(self) { MyRepository(service: self.networkService()) }
    }
    var networkService: Factory<Networking> {
        Factory(self) { MyNetworkService() }
    }
}

@main
struct FactoryDemoApp: App {
    let viewModel = MyViewModel(repository: Container.shared.myRepository())
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView(viewModel: viewModel)
            }
        }
    }
}

Factory is flexible, and it doesn't tie you down to a specific dependency injection pattern or technique.

See Resolutions for more examples.

Mocking

If we go back and look at our original view model code one might wonder why we've gone to all of this trouble? Why not simply say let myService = MyService() and be done with it?

Or keep the container idea, but write something similar to this…

extension Container {
    static var myService: MyServiceType { MyService() }
}

Well, the primary benefit one gains from using a container-based dependency injection system is that we're able to change the behavior of the system as needed. Consider the following code:

struct ContentView: View {
    @StateObject var model = ContentViewModel()
    var body: some View {
        Text(model.text())
            .padding()
    }
}

Our ContentView uses our view model, which is assigned to a StateObject. Great. But now we want to preview our code. How do we change the behavior of ContentViewModel so that its MyService dependency isn't making live API calls during development?

It's easy. Just replace MyService with a mock that also conforms to MyServiceType.

#Preview {
    let _ = Container.shared.myService.register { MockService2() }
    ContentView()
}

Note the line in our preview code where we’re gone back to our container and registered a new closure on our factory. This function overrides the default factory closure.

Now when our preview is displayed ContentView creates a ContentViewModel which in turn has a dependency on myService using the Injected property wrapper. And when the wrapper asks the factory for an instance of MyServiceType it now gets a MockService2 instead of the MyService type originally defined.

This is a powerful concept that lets us reach deep into a chain of dependencies and alter the behavior of a system as needed.

Note that Factory 2.5.1 made it even cleaner.

#Preview {
    Container.shared.myService.preview { MockService2() }
    ContentView()
}

See the Previews documentation for more.

Testing

The same concept can be used when writing unit tests. Consider the following.

final class FactoryCoreTests: XCTestCase {

    override func setUp() {
        super.setUp()
        Container.shared.reset()
    }
    
    func testLoaded() throws {
        Container.shared.accountProvider.register { MockProvider(accounts: .sampleAccounts) }
        let model = Container.shared.someViewModel()
        model.load()
        XCTAssertTrue(model.isLoaded)
    }

    func testEmpty() throws {
        Container.shared.accountProvider.register { MockProvider(accounts: []) }
        let model = Container.shared.someViewModel()
        model.load()
        XCTAssertTrue(model.isEmpty)
    }

    func testErrors() throws {
        Container.shared.accountProvider.register { MockProvider(error: .notFoundError) }
        let model = Container.shared.someViewModel()
        model.load()
        XCTAssertTrue(model.errorMessage == "Some Error")
    }
    
}

Again, Factory makes it easy to reach into a chain of dependencies and make specific changes to the system as needed. This makes testing loading states, empty states, and error conditions simple.

Xcode 16 Testing

Factory also works with Apple's new Testing framework, and with Xcode 16.3's new test trait support it's now also possible to run tests in parallel!

Here's the same set of tests updated for the new framework. The .container trait provides a new, fresh instance of the main shared container to each one of the tests.

@Suite(.container) // added container trait
struct FactoryTests {

    @Test func testLoaded
View on GitHub
GitHub Stars2.8k
CategoryDevelopment
Updated21h ago
Forks179

Languages

Swift

Security Score

100/100

Audited on Mar 31, 2026

No findings