Tranquillity

# DITranquillity Tranquillity is a simple but powerful [dependency injection](https://en.wikipedia.org/wiki/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](README.md), [Russian](README_ru.md) ## What is Dependency Injection? [Dependency Injection (DI)](https://en.wikipedia.org/wiki/Dependency_injection) is a design pattern where someone delivers dependencies to an object. It is a specific form of [Inversion of Control (IoC)](https://en.wikipedia.org/wiki/Inversion_of_control) and helps implement the [Dependency Inversion Principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle). For more details, you can [read here](Documentation/en/about_dependency_injection.md). I also recommend checking out the [glossary](Documentation/en/glossary.md), which will help you better understand the terms. ## Features ### Core - [x] [Component and service registration](Documentation/en/core/registration_and_service.md) - [x] [Initializer, property, and method injection](Documentation/en/core/injection.md) - [x] [Optional injection, with arguments, multiple, by tag/name](Documentation/en/core/modificated_injection.md) - [x] [Delayed injection (Lazy, Provider)](Documentation/en/core/delayed_injection.md) - [x] [Circular dependency injection](Documentation/en/core/injection.md#property-injection) - [x] [Lifetime management](Documentation/en/core/scope_and_lifetime.md) - [x] [Modularity support](Documentation/en/core/modular.md) - [x] [Complete and detailed logging](Documentation/en/core/logs.md) - [x] Thread-safe operation from multiple threads - [x] **Swift Concurrency (async/await, @MainActor)** - [x] [Container hierarchy](Documentation/en/core/container_hierarchy.md) ### Graph API - [x] [Getting the dependency graph](Documentation/en/graph/get_graph.md) - [x] [Dependency graph validation](Documentation/en/graph/graph_validation.md) - [ ] [Dependency graph visualization](Documentation/en/graph/visualization_graph.md) ## 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: ```swift .package(url: "https://github.com/ivlevAstef/DITranquillity.git", from: "6.0.0") ``` And specify the dependency in the target: ```swift .product(name: "DITranquillity") ``` > If you have a project on 5.x.x, see the [5 → 6 migration guide](Documentation/en/migration5to6.md). 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 ```swift 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 ```swift import SwiftUI import DITranquillity // MARK: - Protocols protocol APIClient: Sendable { func fetch(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(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: 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 ```swift 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 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 let navigationController: UINavigationController // AsyncProvider for asynchronous creation of @MainActor objects private let viewModelProvider: AsyncProvider private let detailCoordinatorProvider: UserDetailCoordinator.SelfProvider init( navigationController: UINavigationController, viewModelProvider: AsyncProvider, detailCoordinatorProvider: Provider1 ) { 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 let navigationController: UINavigationController private let user: User private let viewModelProvider: AsyncProvider1 init( navigationController: UINavigationController, user: User, viewModelProvider: AsyncProvider1 ) { 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](Samples/SampleHabr/) — basic example - [SampleChaos](Samples/SampleChaos/) — complex scenarios - [SampleDelegateAndObserver](Samples/SampleDelegateAndObserver/) — delegate and observer patterns - [FunCorpSteamApp](https://github.com/ivlevAstef/FunCorpSteamApp) — SwiftPM with large architecture ## Articles - [Modern article on Habr (Russian)](https://habr.com/ru/post/457188/) - [Old article (Russian)](https://habr.com/ru/post/311334/) ## 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](Documentation/en/migration5to6.md) for details. ## Changelog See [CHANGELOG](CHANGELOG.md) or the [releases](https://github.com/ivlevAstef/DITranquillity/releases) tab. ## History and Plans - [x] v1.x.x — Initial version - [x] v2.x.x — Stabilization ([migration](Documentation/ru/migration1to2.md)) - [x] v3.x.x — Evolution and features ([migration](Documentation/ru/migration2to3.md)) - [x] v4.x.x — Optimization ([migration](Documentation/ru/migration3to4.md)) - [x] v5.x.x — preconcurrency Swift Concurrency (@MainActor, Sendable) - [x] v6.x.x — **Swift Concurrency (async/await, @MainActor, Sendable)** ([migration](Documentation/en/migration5to6.md)) ## Feedback ### Found a bug or want a new feature? Create an issue in the [GitHub Issues](https://github.com/ivlevAstef/DITranquillity/issues) tab. ### Found a documentation issue or know how to improve the library? Create a [pull request](https://github.com/ivlevAstef/DITranquillity/pulls). ### Like the library? Give it a star on GitHub! ### Have questions? Email: ivlev.stef@gmail.com