Skip to content

Swinject

Developer UtilitiesDependency InjectionSwift

What it is

Swinject is a dependency injection framework for Swift, using a container with registered factories, object scopes and assemblies.

Register factories against protocols with an object scope, then resolve. Assemblies group related registrations into modules.

Installation

.package(url: "https://github.com/Swinject/Swinject.git", from: "2.9.1")

Getting started

The smallest useful thing you can do with it, and what each part means.

Registration and resolution
let container = Container()

container.register(APIClient.self) { _ in
    URLSessionAPIClient(session: .shared)
}.inObjectScope(.container)      // singleton for the container's lifetime

container.register(BookRepository.self) { r in
    RemoteBookRepository(api: r.resolve(APIClient.self)!)
}

container.register(BookViewModel.self) { r in
    BookViewModel(repository: r.resolve(BookRepository.self)!)
}.inObjectScope(.transient)      // a new one each time

let viewModel = container.resolve(BookViewModel.self)!
resolve returns an optional and the force unwrap is conventional — but it means a missing registration is a crash at runtime rather than a compile error, which is Swinject's main trade-off.

Advanced usage

Where the library earns its place over a simpler alternative.

Assemblies and test overrides
final class NetworkAssembly: Assembly {
    func assemble(container: Container) {
        container.register(APIClient.self) { _ in URLSessionAPIClient() }
            .inObjectScope(.container)
    }
}

final class DataAssembly: Assembly {
    func assemble(container: Container) {
        container.register(BookRepository.self) { r in
            RemoteBookRepository(api: r.resolve(APIClient.self)!)
        }
    }
}

let assembler = Assembler([NetworkAssembly(), DataAssembly()])

// Tests: register over the real binding.
let testAssembler = Assembler([NetworkAssembly(), DataAssembly()])
testAssembler.container.register(APIClient.self) { _ in MockAPIClient() }
A later registration for the same type replaces the earlier one, which is how tests substitute fakes without rebuilding the whole graph.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Unexpectedly found nil when resolving
The type was never registered, or a different protocol was used. Swinject cannot catch this at compile time — add a resolution smoke test.
Objects are retained longer than expected
An overly broad object scope. Use .transient or .graph unless a singleton is genuinely intended.

Best practices

  • Group registrations into Assemblies so the composition root stays readable.
  • Choose scopes deliberately — .container is a singleton and will retain whatever it holds.
  • Resolve only at the composition root; passing the container around is a service locator.
  • Add a test that resolves every registered type, since resolution failures are runtime-only.

Background

Why it exists, and what it was reacting to.

Swinject is the long-standing DI container for Swift, predating the property-wrapper approaches. Its assembly concept keeps registration organised in large applications.