This is a command line tool for compile-time Automatic Dependency Injection for Swift.
It reads sw: annotations in the Swift source code and generates Containers with your resolved dependencies.
Attention: This tool is still in active development and is experimental. I don't recommend adopting it in your project yet. Check the TO-DO list below for some of the things that still need to be implemented.
Most automatic dependency injection tools for Swift either only work in runtime and provide no compile-time guarantee. Other compile-time tools require changes to the code, and require the project to use specific Swift Macros and use Swift Package Manager.
Swift Wiring doesn't modify the existing code, and it doesn't require any other code dependencies to be added to your project.
It aims to be:
-
Non-intrusive
- All of its annotations and commands live in comments.
-
Additive
- It does not modify your existing source code, and it only generates new code.
-
Simple
- It has only a few commands.
- It won't check every error, so it relies on the Swift compiler to ultimately verify the generated Container code.
- The generated code is human-readable and can be debugged.
Navigate to the Example/ folder and use XcodeGen to generate an Xcode project of an iOS app that uses this tool.
Example Input
import Foundation
// sw: container(MyContainer) {
// access(public)
//
// singleton(SessionManager)
// singleton(UserManager) { access(public) }
//
// singleton(UserDataPersistence, PersistenceProtocol) { name(User) }
// singleton(SessionDataPersistence, PersistenceProtocol) { name(Session) }
//
// build(NetworkManager, NetworkManagerProtocol)
// build(AuthNetworkManager, NetworkManagerProtocol) { name(Authenticated) }
//
// build(LoggedOutApi) { access(public) }
// build(OtherApi, ApiClient) { access(public) name(Other) }
// build(UserInfoApi, ApiClient) { name(User) }
//
// build(providesUserName) { name(UserName) }
// build(providesUserEmail, String) { name(UserEmail) }
// build(UserViewModel)
// }
protocol MyContainerProtocol {
}
public protocol ApiClient {}
protocol SomethingExternal {}
// sw: inject
final class LoggedOutApi: ApiClient {
let networkManager: NetworkManagerProtocol
let something: SomethingExternal
let parameter: String
init(
// sw: dependency
networkManager: NetworkManagerProtocol,
// sw: dependency
something: SomethingExternal,
parameter: String
) {
self.networkManager = networkManager
self.something = something
self.parameter = parameter
}
}
// sw: inject
final class OtherApi: ApiClient {
let networkManager: NetworkManagerProtocol
init(
// sw: dependency
networkManager: NetworkManagerProtocol
) {
self.networkManager = networkManager
}
}
// sw: inject
final class UserInfoApi: ApiClient {
let authNetworkManager: NetworkManagerProtocol
init(
// sw: dependency(Authenticated)
authNetworkManager: NetworkManagerProtocol
) {
self.authNetworkManager = authNetworkManager
}
}
protocol NetworkManagerProtocol {
func perform(request: URLRequest) async throws -> Data
}
// sw: inject
final class NetworkManager: NetworkManagerProtocol {
init() {}
func perform(request: URLRequest) async throws -> Data {
Data()
}
}
// sw: inject
final class AuthNetworkManager: NetworkManagerProtocol {
let sessionManager: SessionManager
init(/* sw: dependency */ sessionManager: SessionManager) {
self.sessionManager = sessionManager
}
func perform(request: URLRequest) async throws -> Data {
Data()
}
}
// sw: inject
public final class UserManager {
let persistence: PersistenceProtocol
let apiClient: ApiClient
let sessionManager: SessionManager
let userName: String
let userEmail: String
init(
/* sw: dependency(User) */ persistence: PersistenceProtocol,
/* sw: dependency(User) */ apiClient: ApiClient,
/* sw: dependency */ sessionManager: SessionManager
) {
self.persistence = persistence
self.apiClient = apiClient
self.sessionManager = sessionManager
self.userName = "Some name"
self.userEmail = "email@email.com"
}
}
// sw: inject
final class SessionManager {
let persistence: PersistenceProtocol
init(/* sw: dependency(Session) */ persistence: PersistenceProtocol) {
self.persistence = persistence
}
}
protocol PersistenceProtocol {
}
// sw: inject
final class SessionDataPersistence: PersistenceProtocol {
init() {}
}
// sw: inject
final class UserDataPersistence: PersistenceProtocol {
init() {}
}
// sw: inject
func providesUserName(
/* sw: dependency */ userManager: UserManager
) -> String {
userManager.userName
}
// sw: inject
func providesUserEmail(
/* sw: dependency */ userManager: UserManager
) -> String {
userManager.userEmail
}
// sw: inject
final class UserViewModel {
let userName: String
let userEmail: String
init(
/* sw: dependency(UserName) */ userName: String,
/* sw: dependency(UserEmail) */ userEmail: String
) {
self.userName = userName
self.userEmail = userEmail
}
}Example Output
import Foundation
public final class MyContainer: MyContainerProtocol {
public struct ExternalDependency<T> {
let closure: () -> T
init(closure: @escaping () -> T) {
self.closure = closure
}
public static func constant(_ value: T) -> Self {
.init(closure: { value })
}
public static func builder(_ closure: @escaping () -> T) -> Self {
.init(closure: closure)
}
}
let externalSomethingExternal: () -> SomethingExternal
private(set) lazy var singletonSessionPersistenceProtocol: PersistenceProtocol = buildSessionPersistenceProtocol()
private(set) lazy var singletonUserPersistenceProtocol: PersistenceProtocol = buildUserPersistenceProtocol()
private(set) lazy var singletonSessionManager: SessionManager = buildSessionManager()
public private(set) lazy var singletonUserManager: UserManager = buildUserManager()
public init(
somethingExternal: ExternalDependency<SomethingExternal>
) {
self.externalSomethingExternal = somethingExternal.closure
}
public func buildOtherApiClient() -> ApiClient {
return OtherApi(
networkManager: self.buildNetworkManagerProtocol()
)
}
internal func buildUserApiClient() -> ApiClient {
return UserInfoApi(
authNetworkManager: self.buildAuthenticatedNetworkManagerProtocol()
)
}
public func buildLoggedOutApi(
parameter: String
) -> LoggedOutApi {
return LoggedOutApi(
networkManager: self.buildNetworkManagerProtocol(),
something: self.externalSomethingExternal(),
parameter: parameter
)
}
internal func buildNetworkManagerProtocol() -> NetworkManagerProtocol {
return NetworkManager(
)
}
internal func buildAuthenticatedNetworkManagerProtocol() -> NetworkManagerProtocol {
return AuthNetworkManager(
sessionManager: self.singletonSessionManager
)
}
private func buildSessionPersistenceProtocol() -> PersistenceProtocol {
return SessionDataPersistence(
)
}
private func buildUserPersistenceProtocol() -> PersistenceProtocol {
return UserDataPersistence(
)
}
private func buildSessionManager() -> SessionManager {
return SessionManager(
persistence: self.singletonSessionPersistenceProtocol
)
}
internal func buildUserEmailString() -> String {
return providesUserEmail(
userManager: self.singletonUserManager
)
}
private func buildUserManager() -> UserManager {
return UserManager(
persistence: self.singletonUserPersistenceProtocol,
apiClient: self.buildUserApiClient(),
sessionManager: self.singletonSessionManager
)
}
internal func buildUserViewModel() -> UserViewModel {
return UserViewModel(
userName: self.buildUserNameString(),
userEmail: self.buildUserEmailString()
)
}
internal func buildUserNameString() -> String {
return providesUserName(
userManager: self.singletonUserManager
)
}
}- Add Scopes with a collection of containers;
- Support actors and main actors;
- Support multiple initializers;
- Add tests;
etc...
Developed by Fabio de Albuquerque Dela Antonio.
This project relies heavily on Swift Syntax.
