Skip to content

fabio914/swift-wiring

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Swift Wiring

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.

Why?

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.

Documentation

Example

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
        )
    }
}

TO-DOs

  • Add Scopes with a collection of containers;
  • Support actors and main actors;
  • Support multiple initializers;
  • Add tests;

etc...

Credits

Developed by Fabio de Albuquerque Dela Antonio.

This project relies heavily on Swift Syntax.

About

Compile-time safe Automatic Dependency Injection for Swift

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors