An open API service indexing awesome lists of open source software.

https://github.com/fabio914/swift-wiring

Compile-time safe Automatic Dependency Injection for Swift
https://github.com/fabio914/swift-wiring

compile-time-checking dependency-injection ios macos swift

Last synced: 4 months ago
JSON representation

Compile-time safe Automatic Dependency Injection for Swift

Awesome Lists containing this project

README

          

# Swift Wiring

This is a command line tool for compile-time Automatic Dependency Injection for [Swift](https://www.swift.org).

It reads `sw:` annotations in the Swift source code and generates `Container`s 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

* [How to Setup](Docs/SETUP.md)
* [Syntax and Commands](Docs/DOCUMENTATION.md)
* [Example](Example/README.md)

## Example

Navigate to the `Example/` folder and use [XcodeGen](https://github.com/yonaskolb/XcodeGen) to generate an Xcode project of an iOS app that uses this tool.

Example Input

```swift
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

```swift
import Foundation

public final class MyContainer: MyContainerProtocol {

public struct ExternalDependency {
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
) {
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](https://github.com/swiftlang/swift-syntax).