What it is
Factory is a compile-time-safe dependency injection container for Swift, using static properties and property wrappers rather than runtime type lookup.
Declare each dependency as a computed Factory on a Container extension, then inject with @Injected. Scopes control lifetime.
Installation
.package(url: "https://github.com/hmlongco/Factory.git", from: "2.4.0")Getting started
The smallest useful thing you can do with it, and what each part means.
swift
import Factory
extension Container {
var apiClient: Factory<APIClient> {
self { URLSessionAPIClient() }.singleton
}
var bookRepository: Factory<BookRepository> {
self { RemoteBookRepository(api: self.apiClient()) }
}
}
final class BookViewModel {
// Resolved lazily; the type is checked at compile time.
@Injected(\.bookRepository) private var repository
func load() async throws -> [Book] {
try await repository.fetchAll()
}
}Advanced usage
Where the library earns its place over a simpler alternative.
swift
// Override for a single test, then reset.
final class BookViewModelTests: XCTestCase {
override func setUp() {
super.setUp()
Container.shared.bookRepository.register { MockBookRepository() }
}
override func tearDown() {
Container.shared.reset() // essential, or overrides leak between tests
super.tearDown()
}
}
// Scopes
self { Service() }.singleton // one for the app lifetime
self { Service() }.cached // until reset
self { Service() }.shared // while something holds a strong reference
self { Service() } // new every time (default)Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- Tests interfere with each other
- A registration override persisted. Call Container.shared.reset() in tearDown.
- A circular dependency causes a stack overflow
- Two factories resolve each other. Break the cycle with a lazy property or a protocol boundary.
Best practices
- Always reset the container in tearDown so overrides do not leak between tests.
- Prefer @Injected over resolving manually so dependencies are visible on the type.
- Choose the narrowest scope that works; .singleton retains whatever it holds forever.
- Consider Factory over Swinject when compile-time safety matters more than assembly organisation.
Background
Why it exists, and what it was reacting to.
Factory was written as a response to Swinject's runtime resolution: because dependencies are declared as typed static properties, a missing registration is a compile error rather than a crash.
