DITranquillity

May 7, 2026 · View on GitHub

Tranquillity

DITranquillity

Tranquillity is a simple but powerful dependency injection library for Swift.

The name "Tranquillity" was chosen deliberately — it embodies three fundamental principles of the library: clarity, simplicity, and safety.

It says — use this library and you'll be calm about your dependencies.

Language switch: English, Russian

What is Dependency Injection?

Dependency Injection (DI) is a design pattern where someone delivers dependencies to an object.

It is a specific form of Inversion of Control (IoC) and helps implement the Dependency Inversion Principle.

For more details, you can read here.

I also recommend checking out the glossary, which will help you better understand the terms.

Features

Core

Graph API

Installation

The library supports SwiftPM and Carthage.

Note: Starting from version 5.0.0, CocoaPods is not supported.

Use "Xcode → File → Swift Packages → Add Package Dependency..." and specify the URL:

https://github.com/ivlevAstef/DITranquillity

Or add to Package.swift in the dependencies section:

.package(url: "https://github.com/ivlevAstef/DITranquillity.git", from: "6.0.0")

And specify the dependency in the target:

.product(name: "DITranquillity")

If you have a project on 5.x.x, see the 5 → 6 migration guide. 5.x.x is still available by tags, but no new releases will be published. For most projects the migration requires no code changes.

Carthage

Add to your Cartfile:

github "ivlevAstef/DITranquillity"

Usage

The library uses a declarative style for describing dependencies and allows you to separate application code from dependency description code.

Simple Example

import DITranquillity

// Services
class Logger {
    func log(_ message: String) {
        print("[\(Date())] \(message)")
    }
}

class UserService {
    private let logger: Logger

    init(logger: Logger) {
        self.logger = logger
    }

    func getUser(id: Int) -> String {
        logger.log("Fetching user \(id)")
        return "User \(id)"
    }
}

// Container configuration
let container = DIContainer()

container.register(Logger.init)
container.register(UserService.init)

// Getting an object
let service: UserService = container.resolve()
print(service.getUser(id: 42))

Modern Example with SwiftUI and Swift Concurrency

import SwiftUI
import DITranquillity

// MARK: - Protocols

protocol APIClient: Sendable {
    func fetch<T: Decodable & Sendable>(endpoint: String) async throws -> T
}

protocol UserRepository: Sendable {
    func getUser(id: Int) async throws -> User
    func getUsers() async throws -> [User]
}

// MARK: - Implementations

final class URLSessionAPIClient: APIClient, Sendable {
    private let baseURL: URL

    init(baseURL: URL = URL(string: "https://api.example.com")!) {
        self.baseURL = baseURL
    }

    func fetch<T: Decodable & Sendable>(endpoint: String) async throws -> T {
        let url = baseURL.appendingPathComponent(endpoint)
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}

final class DefaultUserRepository: UserRepository, Sendable {
    private let apiClient: APIClient

    init(apiClient: APIClient) {
        self.apiClient = apiClient
    }

    func getUser(id: Int) async throws -> User {
        try await apiClient.fetch(endpoint: "users/\(id)")
    }

    func getUsers() async throws -> [User] {
        try await apiClient.fetch(endpoint: "users")
    }
}

// MARK: - ViewModel with @MainActor

@MainActor
final class UserListViewModel: ObservableObject {
    @Published private(set) var users: [User] = []
    @Published private(set) var isLoading = false
    @Published private(set) var error: Error?

    private let repository: UserRepository

    init(repository: UserRepository) {
        self.repository = repository
    }

    func loadUsers() {
        guard !isLoading else { return }
        isLoading = true
        error = nil

        Task {
            do {
                users = try await repository.getUsers()
            } catch {
                self.error = error
            }
            isLoading = false
        }
    }
}

// MARK: - SwiftUI View

struct UserListView: View {
    @StateObject private var viewModel: UserListViewModel

    init(viewModel: UserListViewModel) {
        _viewModel = StateObject(wrappedValue: viewModel)
    }

    var body: some View {
        NavigationView {
            Group {
                if viewModel.isLoading {
                    ProgressView()
                } else if let error = viewModel.error {
                    Text("Error: \(error.localizedDescription)")
                } else {
                    List(viewModel.users) { user in
                        Text(user.name)
                    }
                }
            }
            .navigationTitle("Users")
        }
        .onAppear {
            viewModel.loadUsers()
        }
    }
}

// MARK: - DI Configuration

enum AppDI {
    static let container: DIContainer = {
        let container = DIContainer()

        // API Layer
        container.register(URLSessionAPIClient.init)
            .as(APIClient.self)
            .lifetime(.single)

        // Repository Layer
        container.register(DefaultUserRepository.init)
            .as(UserRepository.self)
            .lifetime(.perContainer)

        // ViewModel Layer
        container.register(UserListViewModel.init)

        // Validation in debug
        #if DEBUG
        assert(container.makeGraph().checkIsValid(), "DI Graph is invalid!")
        #endif

        return container
    }()
}

// MARK: - App Entry Point

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            // Asynchronous ViewModel creation
            AsyncContentView {
                await AppDI.container.resolve() as UserListViewModel
            } content: { viewModel in
                UserListView(viewModel: viewModel)
            }
        }
    }
}

// Helper View for async initialization
struct AsyncContentView<Content: View, T>: View {
    @State private var value: T?
    let loader: () async -> T
    let content: (T) -> Content

    var body: some View {
        Group {
            if let value {
                content(value)
            } else {
                ProgressView()
            }
        }
        .task {
            value = await loader()
        }
    }
}

MVVM-C (Coordinator) Example

import UIKit
import DITranquillity

// MARK: - Coordinator Protocol

protocol Coordinator: AnyObject {
    var navigationController: UINavigationController { get }
    func start()
}

// MARK: - App Coordinator

final class AppCoordinator: Coordinator {
    typealias SelfProvider = Provider1<AppCoordinator, UINavigationController>

    let navigationController: UINavigationController

    private let userListCoordinatorProvider: UserListCoordinator.SelfProvider

    init(
        navigationController: UINavigationController,
        userListCoordinatorProvider: UserListCoordinator.SelfProvider
    ) {
        self.navigationController = navigationController
        self.userListCoordinatorProvider = userListCoordinatorProvider
    }

    func start() {
        let coordinator = userListCoordinatorProvider.value(navigationController)
        coordinator.start()
    }
}

// MARK: - Feature Coordinator

final class UserListCoordinator: Coordinator {
    typealias SelfProvider = Provider1<UserListCoordinator, UINavigationController>

    let navigationController: UINavigationController

    // AsyncProvider for asynchronous creation of @MainActor objects
    private let viewModelProvider: AsyncProvider<UserListViewModel>
    private let detailCoordinatorProvider: UserDetailCoordinator.SelfProvider

    init(
        navigationController: UINavigationController,
        viewModelProvider: AsyncProvider<UserListViewModel>,
        detailCoordinatorProvider: Provider1<UserDetailCoordinator, User>
    ) {
        self.navigationController = navigationController
        self.viewModelProvider = viewModelProvider
        self.detailCoordinatorProvider = detailCoordinatorProvider
    }

    func start() {
        Task { @MainActor in
            let viewModel = await viewModelProvider.value
            viewModel.onUserSelected = { [weak self] user in
                self?.showUserDetail(user)
            }

            let viewController = UserListViewController(viewModel: viewModel)
            navigationController.pushViewController(viewController, animated: true)
        }
    }

    private func showUserDetail(_ user: User) {
        let coordinator = detailCoordinatorProvider.value(navigationController, user)
        coordinator.start()
    }
}

final class UserDetailCoordinator: Coordinator {
    typealias SelfProvider = Provider1<UserDetailCoordinator, UINavigationController, User>

    let navigationController: UINavigationController

    private let user: User
    private let viewModelProvider: AsyncProvider1<UserDetailViewModel, User>

    init(
        navigationController: UINavigationController,
        user: User,
        viewModelProvider: AsyncProvider1<UserDetailViewModel, User>
    ) {
        self.navigationController = navigationController
        self.viewModelProvider = viewModelProvider
    }

    func start() {
        ...
    }
}

// MARK: - DI Configuration

final class AppDIFramework: DIFramework {
    static func load(container: DIContainer) {
        container.append(part: NetworkPart.self)
        container.append(part: RepositoryPart.self)
        container.append(part: ViewModelPart.self)
        container.append(part: CoordinatorPart.self)
    }
}

final class NetworkPart: DIPart {
    static func load(container: DIContainer) {
        container.register(URLSessionAPIClient.init)
            .as(APIClient.self)
            .lifetime(.single)
    }
}

final class RepositoryPart: DIPart {
    static func load(container: DIContainer) {
        container.register(DefaultUserRepository.init)
            .as(UserRepository.self)
            .lifetime(.perContainer)
    }
}

final class ViewModelPart: DIPart {
    static func load(container: DIContainer) {
        container.register(UserListViewModel.init)
        container.register(UserDetailViewModel.init) { arg(\$0) }
    }
}

final class CoordinatorPart: DIPart {
    static func load(container: DIContainer) {
        container.register(AppCoordinator.init) { arg(\$0) }
            .root()  // Entry point

        container.register(UserListCoordinator.init) { arg(\$0) }

        container.register { UserDetailCoordinator(
            navigationController: arg(\$0),
            user: arg(\$1),
            viewModelProvider: \$2
        )}
    }
}

// MARK: - App Delegate

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    private var appCoordinator: AppCoordinator?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        let container = DIContainer()
        container.append(framework: AppDIFramework.self)

        #if DEBUG
        assert(container.makeGraph().checkIsValid(), "DI configuration is invalid!")
        #endif

        let window = UIWindow(frame: UIScreen.main.bounds)
        let navigationController = UINavigationController()
        window.rootViewController = navigationController

        // Inject navigationController as an argument
        appCoordinator = container.resolve(arg: navigationController)
        appCoordinator?.start()

        self.window = window
        window.makeKeyAndVisible()

        return true
    }
}

Sample Projects

Articles

Requirements

iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 8.0+, Linux; ARC

SwiftXcodeDITranquillity
6.0+26+>= 6.0.0
6.0+16+>= 5.0.0
5.5-5.913-15>= 3.6.3
5.0-5.310.2-12.x>= 3.6.3

Version 6.0.0 and above can use async/await, but it's then recommended to raise the deployment target to iOS 17.0+, macOS 15.0+, tvOS 17.0+, watchOS 10.0+. See the 5 → 6 migration guide for details.

Changelog

See CHANGELOG or the releases tab.

History and Plans

  • v1.x.x — Initial version
  • v2.x.x — Stabilization (migration)
  • v3.x.x — Evolution and features (migration)
  • v4.x.x — Optimization (migration)
  • v5.x.x — preconcurrency Swift Concurrency (@MainActor, Sendable)
  • v6.x.x — Swift Concurrency (async/await, @MainActor, Sendable) (migration)

Feedback

Found a bug or want a new feature?

Create an issue in the GitHub Issues tab.

Found a documentation issue or know how to improve the library?

Create a pull request.

Like the library?

Give it a star on GitHub!

Have questions?

Email: ivlev.stef@gmail.com