Getting Started
Install ForgeInject, register your first dependency, and resolve it with @Injectable.
Requirements
- iOS 18+ / macOS 15+
- Swift 6.3+ (Xcode 26 or later)
Installation
- Open your project in Xcode.
- Go to File → Add Package Dependencies…
- Paste the repository URL:
https://github.com/stefanprojchev/ForgeInject.git - Set the version rule to Up to Next Major from
1.0.0. - Select your app target and click Add Package.
dependencies: [
.package(url: "https://github.com/stefanprojchev/ForgeInject.git", from: "1.0.0")
] targets: [
.target(
name: "YourApp",
dependencies: ["ForgeInject"]
)
] swift package add-dependency https://github.com/stefanprojchev/ForgeInject.git --from 1.0.0 Register Dependencies
Create a struct conforming to ForgeRegisterProtocol to hold your registrations. This separates registration logic from your app entry point and scales cleanly as your app grows.
import ForgeInject
struct AppDependencies: ForgeRegisterProtocol {
func registerDependencies(in container: ForgeContainerProtocol) {
container.register(with: .singleton) { _ in
GreetingService() as GreetingServiceProtocol
}
}
} Initialize the container at app launch and call your registration struct:
import SwiftUI
import ForgeInject
@main
struct TaskFlowApp: App {
init() {
let container = ForgeContainer()
AppDependencies().registerDependencies(in: container)
ForgeContainer.shared = container
}
var body: some Scene {
WindowGroup {
GreetingView()
}
}
} import UIKit
import ForgeInject
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let container = ForgeContainer()
AppDependencies().registerDependencies(in: container)
ForgeContainer.shared = container
return true
}
} Set once: ForgeContainer.shared can only be set once per app launch. Subsequent non-nil assignments trigger a precondition failure.
Consume Dependencies
Apply @Injectable to any class, struct, or actor with let stored properties. The macro generates a matching init where each parameter defaults to ForgeContainer.shared's resolved value.
import ForgeInject
@Injectable
@Observable
final class GreetingViewModel {
let greetingService: GreetingServiceProtocol
var message = ""
func sayHello() {
message = greetingService.greet(name: "World")
}
} Production vs Tests
Because the generated init accepts explicit arguments, tests can swap mocks in without touching the container at all.
// Production — zero-arg init resolves from ForgeContainer.shared
let viewModel = GreetingViewModel()
// Tests — pass a mock directly, no container touching
let viewModel = GreetingViewModel(greetingService: MockGreetingService()) Next: ForgeInject ships three macros — @Injectable, @Inject, and #inject() — each suited to a different scenario. See the Macros page to learn when to use which.