Dependency Injection Container Swift: Swinject for iOS/macOS

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Dependency Injection Container for Swift (iOS/macOS): Swinject

In modern iOS and macOS development with Swift, architectural patterns play a key role in creating maintainable and testable code. One of the most powerful tools for implementing the Dependency Inversion Principle is Swinject — a lightweight yet functional DI container written in Swift. Swinject allows centralized management of object creation and their dependencies, freeing the developer from manually constructing the object graph.



What is Dependency Injection and why do you need a container?

Dependency Injection (DI) is a technique where an object receives its dependencies from the outside rather than creating them itself. This reduces code coupling and simplifies unit testing. However, in large projects, manual dependency injection leads to cumbersome code. This is where a DI container comes to the rescue, which:

  • Automates object creation and binding
  • Manages object lifetimes (singletons, new instances)
  • Simplifies implementation swapping (e.g., for tests)
  • Improves code readability and maintainability


Installing Swinject

Swinject can be installed via Swift Package Manager, CocoaPods, or Carthage. For SPM, add to Package.swift:

dependencies: [    .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")]

Or via CocoaPods:

pod 'Swinject', '~> 2.8'


Core Concepts of Swinject

The library is based on several key components:

  • Container — the main registrar and resolver of dependencies
  • Service — the type being registered (usually a protocol or class)
  • Component — the concrete implementation of a service
  • Scope — defines the object lifetime (transient, graph, container, weak)


Practical Example: Registration and Resolution of Dependencies

Consider a typical scenario — an application with an authentication service and a controller that uses it.

// Service protocolprotocol AuthServiceProtocol {    func login(username: String, password: String) -> Bool}

// Implementationclass AuthService: AuthServiceProtocol { func login(username: String, password: String) -> Bool { // Authentication logic return username == "admin" && password == "1234" }}

// Controller depending on AuthServiceProtocolclass LoginViewController { private let authService: AuthServiceProtocol // Injection via initializer init(authService: AuthServiceProtocol) { self.authService = authService } func authenticate(user: String, pass: String) { if authService.login(username: user, password: pass) { print("Successful login") } else { print("Invalid credentials") } }}

// Container setuplet container = Container()container.register(AuthServiceProtocol.self) { _ in AuthService() }container.register(LoginViewController.self) { r in LoginViewController(authService: r.resolve(AuthServiceProtocol.self)!)}

// Usagelet controller = container.resolve(LoginViewController.self)!controller.authenticate(user: "admin", pass: "1234") // Successful login


Managing Object Lifetimes (Scopes)

Swinject supports four built-in scopes:

  • .transient — a new instance is created each time (default)
  • .graph — one instance per resolution graph (used by default for most cases)
  • .container — singleton within the container
  • .weak — weak reference, object

Recommendations