FI
ForgeInject

Modular Registration

Organize registrations by feature module as your app grows.

The Protocol

ForgeInject provides ForgeRegisterProtocol for grouping related registrations into self-contained modules. Each module registers only the dependencies it owns.

public protocol ForgeRegisterProtocol {
    func registerDependencies(in container: ForgeContainerProtocol)
}

Feature Modules

Create one struct per feature area. Each module is responsible for its own dependencies.

NetworkModule.swift
import ForgeInject

struct NetworkModule: ForgeRegisterProtocol {
    func registerDependencies(in container: ForgeContainerProtocol) {
        container.register(with: .singleton) { _ in
            URLSession.shared as URLSessionProtocol
        }

        container.register(with: .singleton) { container in
            let session: URLSessionProtocol = try container.resolve()
            return NetworkService(session: session) as NetworkServiceProtocol
        }
    }
}
UserModule.swift
import ForgeInject

struct UserModule: ForgeRegisterProtocol {
    func registerDependencies(in container: ForgeContainerProtocol) {
        container.register(with: .singleton) { container in
            let networkService: NetworkServiceProtocol = try container.resolve()
            return UserRepository(networkService: networkService) as UserRepositoryProtocol
        }

        container.register(with: .transient) { container in
            let repository: UserRepositoryProtocol = try container.resolve()
            return UserService(repository: repository) as UserServiceProtocol
        }
    }
}

Root Composition

Create a root AppDependencies struct that composes all feature modules. This is the single entry point for all registrations — your app entry point only ever calls this one struct. See the Modular App example for a full implementation including an AuthModule.

AppDependencies.swift
import ForgeInject

struct AppDependencies: ForgeRegisterProtocol {
    func registerDependencies(in container: ForgeContainerProtocol) {
        NetworkModule().registerDependencies(in: container)
        AuthModule().registerDependencies(in: container)
        UserModule().registerDependencies(in: container)
    }
}
TaskFlowApp.swift
import SwiftUI
import ForgeInject

@main
struct TaskFlowApp: App {
    init() {
        let container = ForgeContainer()
        AppDependencies().registerDependencies(in: container)
        ForgeContainer.shared = container
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Note: Registration order does not matter. Dependencies are resolved lazily on first access, not at registration time. However, grouping modules by dependency layer (infrastructure → domain → feature) improves readability.

Consuming Modular Dependencies

Once registered, any @Injectable type automatically picks up the right dependency by its property type — modules are transparent at the consumption site.

Best Practices

  • One module per feature area — network, auth, user, tasks, etc. Modules should not know about each other except through the shared container.
  • Cross-module dependencies via protocols only — never import a concrete type from another module's internals.
  • Group modules by layer in your root composition — infrastructure (network) first, then domain (auth/user), then feature-specific.