DITranquillity
May 7, 2026 · View on GitHub
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.
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
- Component and service registration
- Initializer, property, and method injection
- Optional injection, with arguments, multiple, by tag/name
- Delayed injection (Lazy, Provider)
- Circular dependency injection
- Lifetime management
- Modularity support
- Complete and detailed logging
- Thread-safe operation from multiple threads
- Swift Concurrency (async/await, @MainActor)
- Container hierarchy
Graph API
Installation
The library supports SwiftPM and Carthage.
Note: Starting from version 5.0.0, CocoaPods is not supported.
SwiftPM (Recommended)
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
- SampleHabr — basic example
- SampleChaos — complex scenarios
- SampleDelegateAndObserver — delegate and observer patterns
- FunCorpSteamApp — SwiftPM with large architecture
Articles
Requirements
iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 8.0+, Linux; ARC
| Swift | Xcode | DITranquillity |
|---|---|---|
| 6.0+ | 26+ | >= 6.0.0 |
| 6.0+ | 16+ | >= 5.0.0 |
| 5.5-5.9 | 13-15 | >= 3.6.3 |
| 5.0-5.3 | 10.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