Your Swift project hit 20,000 lines and the AppDelegate has become a god object that news every service, every repository, and every view model in the app. You know you need dependency injection — but picking a framework feels like a second full-time job. Swinject has been the default since 2015, Uber open-sourced Needle for compile-time safety, and Factory is the new container-based kid that ships with SwiftUI support out of the box. In 2026 the choice is less about “which library” and more about “which trade-off you can live with” — runtime flexibility, build-time code generation, or a small compile-time-safe container that does neither.

TL;DR / Quick Verdict

Choose Factory if you want compile-time safety without a code generator — it is the best all-rounder for SwiftUI apps today (2,890 stars, actively maintained). Choose Needle if you are Uber-scale: multi-million-line codebases, RIBs architecture, and you accept a build-time generator in your Xcode pipeline (2,011 stars). Choose Swinject only if you need maximum runtime flexibility — dynamic registrations, container hierarchies, storyboard injection — and you can tolerate crashes at runtime instead of at compile time (6,705 stars, but its last push was September 2025 and the original maintainer has moved on). For 90% of new apps in 2026, Factory is the answer.

Quick Comparison Table

CriterionSwinjectNeedle (Uber)Factory
GitHub stars6,7052,0112,890
Last updateSep 2025Apr 2026Aug 2026
Safety modelRuntime resolutionCompile-time (codegen)Compile-time (property wrappers)
Code generationNoneYes (needle generator)None
PlatformsiOS, macOS, tvOS, watchOS, LinuxiOS, macOSiOS, macOS, tvOS, watchOS, Linux
SwiftUI supportManual (via extensions)ManualFirst-class (@Injected)
ScopesTransient, graph, container, hierarchyHierarchical onlyContainer scopes: shared, singleton, cached, custom
Circular dependenciesSupportedNot supportedSupported (with care)
Storyboard injectionYes (SwinjectStoryboard)NoNo (use view model factories)
LicenseMITApache 2.0MIT
Code size~largeframework + generatorunder 1,000 lines

Decision Matrix: Which One for Your Case?

Use CaseRecommendationWhy
New SwiftUI app in 2026Factory@Injected wrappers, previews work out of the box, zero build phases
Existing UIKit app with storyboardsSwinjectSwinjectStoryboard does automatic injection, no codegen
Multi-team, multi-million-line codebaseNeedle“If it compiles, it works” — refactors fail at build time, not in production
RIBs / Uber-style architectureNeedleDesigned alongside RIBs, hierarchical scopes match the tree
App that needs dynamic registrations at runtimeSwinjectRuntime container allows late binding and plugin-style loading
Small library or framework with few depsFactoryMinimal footprint, no generator step to add to CI
You hate extra build phasesFactory or SwinjectNeither requires codegen; Needle adds a generator run script

Swinject — The Runtime Veteran

Swinject is the oldest and most popular pure-Swift DI framework, originally created by Yoichi Tagaya and now maintained by the Faire mobile platform team. Its model is a classic runtime container: you register closures that construct your types, then resolve them by type at runtime. This gives you enormous flexibility — registrations can be added or replaced at any point, scopes can be transient, graph, container, or hierarchical, and even circular dependencies are supported.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import Swinject

let container = Container()
container.register(Animal.self) { _ in Cat(name: "Mimi") }
container.register(Person.self) { r in
    PetOwner(pet: r.resolve(Animal.self)!)
}

// Resolution happens at runtime:
let person = container.resolve(Person.self)!
print(person.pet.name) // "Mimi"

Installation is straightforward with Swift Package Manager:

1
2
3
4
// Package.swift
dependencies: [
    .package(url: "https://github.com/Swinject/Swinject.git", from: "2.9.1")
]

The cost of that flexibility is safety. A misspelled type, a missing registration, or a scope mistake surfaces as a force-unwrap crash or a fatal error at runtime — usually in the hands of a tester or a user, not in CI. The ContainerHierarchy feature lets you parent containers so child containers can fall back to parent registrations, which is useful for modular apps but adds another layer where resolution can silently fail. As of September 2025 the repository has slowed down considerably; the 6,705-star community still answers issues, but new feature velocity has dropped, which matters if you are starting a project that will live for five years.

Needle — Uber’s Compile-Time Generator

Needle takes the opposite philosophy: if it compiles, it works. Instead of a runtime lookup table, Needle generates Swift code at build time that wires your dependency graph together. You write plain Swift Component classes and Dependency protocols; a command-line generator (written in Swift, installable via Homebrew) produces the glue code. This is the same approach Google’s Dagger takes on the JVM, and it is the reason Uber trusts it inside apps with millions of lines of code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import NeedleFoundation

/// This protocol encapsulates the dependencies acquired from ancestor scopes.
protocol MyDependency: Dependency {
    var chocolate: Food { get }
    var milk: Food { get }
}

/// This class defines a new dependency scope that can acquire dependencies
/// from ancestor scopes via its dependency protocol.
class MyComponent: Component<MyDependency> {
    var hotChocolate: Drink {
        return HotChocolate(dependency.chocolate, dependency.milk)
    }

    var myChildComponent: MyChildComponent {
        return MyChildComponent(parent: self)
    }
}

The generator is integrated via a build phase:

1
2
3
4
brew install needle
# Add a Run Script build phase:
#   "$(brew --prefix)/bin/needle" generate Sources/DI/Generated.swift \
#     --header-doc "// Generated by Needle" --source-paths Sources/

Needle’s hierarchical model mirrors your app’s object tree: each Component creates its child scopes, and dependencies flow down through Dependency protocols. Because the graph is compiled, renaming a dependency breaks the build immediately — every team member gets instant feedback instead of a runtime crash. The downsides are real: you must add a generator step to Xcode and CI, generated code lives in your repo, and circular dependencies are impossible by design. If your app is a handful of screens with a small service layer, the generator is overhead you do not need.

Factory — The Modern Container for SwiftUI

Factory is a container-based DI framework by Michael Long that in my opinion hits the sweet spot for 2026. It is compile-time safe without any code generation: you declare a Factory inside an extension on Container, and resolution uses Swift’s type system plus property wrappers. If a factory for a type does not exist, the code does not compile. Registrations take one line, resolution takes one line, and the whole library is under 1,000 lines of executable code with 100% unit test coverage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import Factory

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

class ContentRepository {
    @Injected(\.myService) private var myService
    // ...
}

You can also bypass the property wrapper and call the factory directly — handy inside view models:

1
2
3
4
5
6
7
@Observable
class ContentViewModel {
    @ObservationIgnored
    private let myService = Container.shared.myService()
    @ObservationIgnored
    private let eventLogger = Container.shared.eventLogger()
}

Factory’s scopes cover the practical needs: .shared (singleton), .singleton (thread-safe), .cached (lazy, not thread-safe), .graph, and .custom. For SwiftUI previews and unit tests, you override a factory in a test container — no global mutable state, no +reset() hacks. The project is actively maintained (last push August 2026, version 3.3.2) and its documentation via DocC is the best of the three. The one thing Factory does not give you is dynamic registration at runtime — the container is fixed at compile time, which is exactly why it is safe.

Pitfalls and Migration Notes

Do not mix a runtime container with SwiftUI’s observation model. If you resolve services inside init of @Observable classes, mark the stored properties @ObservationIgnored — otherwise every service becomes an observable dependency and your view model re-renders for no reason (this is the classic Factory + SwiftUI foot-gun).

Force unwraps in Swinject are a code smell detector. If you find yourself writing container.resolve(Foo.self)! in more than a handful of places, your registrations are too scattered. Centralize them in an Assembler with Assembly types — Swinject’s modular assembly support exists exactly for this.

Needle’s generator output must be committed or regenerated in CI. If you add Needle to a team, decide early whether generated files go into version control. If they do not, every clone needs the generator installed and the build phase configured — a surprising source of “works on my machine” breakage. If they do, expect noisy diffs on every dependency change.

Scope leak: singletons in tests. With any of these frameworks, a .shared/.singleton scope that holds a database connection or a user session will bleed between unit tests. Factory’s test-container override pattern handles this cleanly; with Swinject you need to rebuild the container per test; with Needle, hierarchy makes it awkward — plan your test strategy before you pick.

Migrating from Swinject to Factory: the conceptual mapping is Container.registerextension Container { var x: Factory<T> } and resolve@Injected or direct call. You can run both side by side during migration — Factory’s container is independent of Swinject’s — which makes the switch incremental rather than a big-bang rewrite. See our Swift testing frameworks comparison for how these choices interact with your test stack, and the Java/Kotlin DI comparison if you maintain a cross-platform mobile team that already uses Dagger or Koin on the Android side.

Performance Considerations

All three frameworks resolve dependencies fast enough that you will never benchmark them as the bottleneck — but the numbers differ meaningfully at scale. Factory’s resolution is a dictionary lookup into a compile-time-built container, typically sub-microsecond. Swinject adds closure evaluation and optional handling per resolution; fine for a few dozen resolutions per screen, noticeable if you resolve inside a UITableViewCell reuse path. Needle’s generated code is direct property access with zero lookup overhead — the fastest of the three, which is why Uber uses it in hot paths. The real performance cost with Needle is build time: the generator adds seconds to every incremental build, and on a large module graph that compounds. If your CI is already slow, measure the generator’s contribution before committing.

For server-side Swift (Linux), Swinject and Factory both work with SwiftNIO-based stacks; Needle targets Apple platforms first, so keep that in mind for shared core modules. Our Swift HTTP client comparison covers the networking layer you will likely inject through whichever container you choose.

FAQ

Is Swinject still maintained in 2026?

Swinject is in maintenance mode rather than active development — the last meaningful push was September 2025, with the project now stewarded by the Faire mobile team. It still works on current Xcode versions and the community answers issues, but do not expect new features. For a new project, prefer Factory or Needle.

Does Factory require code generation like Needle?

No. Factory achieves compile-time safety with Swift generics and property wrappers — there is no generator binary, no build phase, and no generated files in your repository. That is its main advantage over Needle for teams that want safety without pipeline changes.

Can I use Needle with SwiftUI?

Yes, but it is not the natural fit. Needle’s hierarchical components align with RIBs and tree-based architectures; SwiftUI’s declarative view graph does not map cleanly onto component hierarchies. You can wire the root environment objects through Needle and let SwiftUI’s environment handle the rest, but Factory is the smoother experience for SwiftUI-first teams.

Which framework handles unit testing best?

Factory is the easiest: override any factory in a test container and inject fakes without touching production code. Swinject requires rebuilding the container per test, which is fine but verbose. Needle is the hardest — the compile-time graph means test doubles must be registered at generation boundaries, which pushes you toward protocol-based fake injection.

What about circular dependencies?

Swinject supports circular dependencies explicitly. Factory can handle them if you mark at least one side as .shared and keep the cycle lazy. Needle forbids them by design — the generator will fail your build, which is intentional: cycles are usually a design smell.

Is there a DI framework built into Swift itself?

No. Swift has no built-in DI container, and the language’s philosophy favors explicit composition. Apple’s SwiftUI environment provides a lightweight form of dependency propagation, but it is not a general-purpose DI system — which is exactly why third-party frameworks remain essential for app-scale dependency management.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com