Skip to content

stefanprojchev/ForgeInject

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ForgeInject

Dependency injection for iOS with constructor and property wrapper support.

Swift 6.3+ iOS 18+ macOS 15+ License Release

πŸ“– Full documentation β†’


ForgeInject is a macro-based dependency injection library for iOS. It gives you three ways to resolve dependencies β€” one is probably the right fit for every scenario you'll encounter.

Macro Use when Resolution
@Injectable View models and services with stable dependencies. Constructor injection, swappable in tests. At init time
@Inject Heavy or optional properties that may never be touched. Lazy + cached + thread-safe. On first access
#inject() Local variables, default parameters, let in structs. Where used

Features

  • Three macros for three injection patterns β€” pick whichever suits each dependency
  • Testable by design β€” @Injectable generates an init that accepts overrides, so tests don't need to touch the container
  • Thread-safe β€” @Inject's lazy form is backed by Mutex<T?> from the Synchronization framework
  • Three retain policies β€” .transient, .singleton, .weak
  • Modular registration via ForgeRegisterProtocol for feature-based organization
  • Explicit error handling via ForgeContainerError

Requirements

  • iOS 18+
  • macOS 15+
  • Swift 6.3+ (Xcode 26 or later)

Installation

Xcode

  1. File β†’ Add Package Dependencies…
  2. Paste https://github.com/stefanprojchev/ForgeInject.git
  3. Set rule to Up to Next Major from 1.0.0

Package.swift

dependencies: [
    .package(url: "https://github.com/stefanprojchev/ForgeInject.git", from: "1.0.0")
],
targets: [
    .target(
        name: "YourApp",
        dependencies: ["ForgeInject"]
    )
]

Quick Start

1. Register dependencies

import ForgeInject

struct AppDependencies: ForgeRegisterProtocol {
    func registerDependencies(in container: ForgeContainerProtocol) {
        container.register(with: .singleton) { _ in
            NetworkService() as NetworkServiceProtocol
        }
        container.register(with: .singleton) { _ in
            UserRepository() as UserRepositoryProtocol
        }
    }
}

2. Bootstrap at app launch

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() }
    }
}

3. Inject dependencies

import ForgeInject

@Injectable
@Observable
final class ProfileViewModel {
    let network: NetworkServiceProtocol
    let repository: UserRepositoryProtocol

    var user: User?

    func loadProfile() async {
        user = try? await repository.fetchCurrentUser()
    }
}

// Production β€” zero-arg init resolves from the container
let vm = ProfileViewModel()

// Tests β€” pass mocks directly, no container touching
let vm = ProfileViewModel(
    network: MockNetworkService(),
    repository: MockUserRepository()
)

Which macro to use

@Injectable β€” start here. Generates a matching init with default values resolved from ForgeContainer.shared. Tests override dependencies via normal init arguments β€” no container manipulation required.

@Injectable
final class ImageProcessor {
    let filter: FilterService
    let storage: StorageService
}

@Inject β€” lazy, cached, thread-safe. Use for heavy dependencies, circular dependency breaks, or late-bootstrapped services.

final class AdvancedViewModel {
    @Inject var heavyService: HeavyService          // lazy (default)
    @Inject(lazy: false) var logger: LoggerService   // eager, no Mutex overhead
}

#inject() β€” freestanding, works anywhere a value is expected. Local variables, default parameters, lazy var initializers.

struct RequestHandler {
    let database: Database = #inject()

    func handle() async {
        let logger: Logger = #inject()
        logger.info("handling request")
    }
}

See the Macros documentation for when to use which.

Retain policies

// Singleton β€” shared for the container's lifetime
container.register(with: .singleton) { _ in
    DatabaseService() as DatabaseServiceProtocol
}

// Transient β€” new instance every resolve
container.register(with: .transient) { _ in
    FormValidator()
}

// Weak β€” shared while held, recreated after release
container.register(with: .weak) { _ in
    ImageCache() as ImageCacheProtocol
}

Documentation

The Forge Family

ForgeInject is part of the Forge family of Swift packages for iOS.

Package Description
ForgeCore Thread-safe primitives for iOS Swift packages.
ForgeInject Dependency injection with constructor and property wrapper support.
ForgeObservers Reactive system observers β€” connectivity, lifecycle, keyboard, and more.
ForgeStorage Type-safe key-value, file, and Keychain storage.
ForgeOrchestrator Orchestrate app flows β€” startup gates, data pipelines, and continuous monitors.
ForgePush Push notification management β€” permissions, tokens, and routing.
ForgeLocation Location triggers β€” geofencing, significant changes, and visits.
ForgeBackgroundTasks Background task scheduling and dispatch.

License

ForgeInject is released under the MIT License. See LICENSE.

About

Dependency injection for iOS with constructor and property wrapper support.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages