GitXplorerGitXplorer
s

swift-di

public
3 stars
0 forks
0 issues

Commits

List of commits on branch main.
Verified
358d1ee4dcc6972e2f2fdbef61552488ac2a04c8

Merge pull request #11 from sidepelican/test_on_linux

ssidepelican committed a month ago
Unverified
21d77f0838e900b394fc0ac723408368853851a2

Exclude to build client example on Linux

ssidepelican committed a month ago
Unverified
6fed0e2875b8fd6f91d04450831c05c9df6600ac

matrix label name fix

ssidepelican committed a month ago
Unverified
f663e55e3d0329b4c6b2e571d64e7f929dff4b34

test on macOS and Linux and latest 2 swift versions

ssidepelican committed a month ago
Verified
50fbc7183fd8f109624cac664af9cfdd676300e9

Merge pull request #10 from sidepelican/fix_nomark_get

ssidepelican committed a month ago
Unverified
8fd96c969f23531af5cc5ec1b0ff254cf2da8010

Fix macro test

ssidepelican committed a month ago

README

The README file for this repository.

Swift DI

Swift DI is a component-based DI (Dependency Injection) library designed specifically for Swift.

Features

The DI container is implemented as a value type to take advantage of Swift's features. No external executables are needed; it is implemented using standard Swift language features and macros. By using only Swift features, the DI container can easily adapt to new versions of Swift, and users can also extend it to customize behaviors. Multi-module compliance, Sendable conforming, and instance overwriting are supported.

Getting Started

Adding the dependency

To add a dependency on the package, declare it in your Package.swift:

.package(url: "https://github.com/sidepelican/swift-di.git", from: "1.0.0"),

and to your application target, add DI to your dependencies:

.product(name: "DI", package: "swift-di"),

Using DI

In Swift DI, you need to explicitly prepare a key value for each instance. We recommend providing static Key properties as extensions of AnyKey.

import DI

extension AnyKey {
    // (1) Define DI keys.
    static let name = Key<String>()
    static let foo = Key<Foo>()
}

@Component(root: true)
struct RootComponent {
    // (2) Define value for the key.
    @Provides(.name)
    var name: String { "RootComponent" }

    @Provides(.foo)
    var foo: Foo {
        // (3) Get value using `get`.
        Foo(name: get(.name))
    }

    var childComponent: ChildComponent {
        ChildComponent(parent: self)
    }
}

@Component
struct ChildComponent {
    // (4) Child component can override parent component's key.
    @Provides(.name)
    var name: String { "ChildComponent" }

    func testFooName() {
        print(get(.foo)) // => Foo(name: "ChildComponent")
    }
}

Use Priority

When priority is set, values with a lower priority will not overwrite those with a higher priority.

@Component(root: true)
struct TestComponent {
    @Provides(.name, priority: .test)
    var name: String { "TestComponent" }
}

@Component
struct AppComponent {
    @Provides(.name, priority: .default)
    var name: String { "AppComponent" }

    func printName() {
        print(get(.name))
    }
}

let component = AppComponent(parent: TestComponent())
component.printName() // TestComponent

Manually Bind Value

By using bind(_:forKey:), you can procedurally override values.

@Component
struct ParentComponent {
    @Provides(.name)
    var name: String = "ParentComponent"

    @Provides(.age)
    var age: Int = 42
}

@Component
struct ChildComponent {
    @Provides(.name)
    var name: String = "ChildComponent"

    func printProfile() {
        print("name=\(get(.name)), age=\(get(.age))")
    }
}

var component = ChildComponent(parent: ParentComponent())
component.printProfile() // name=ChildComponent, age=42
component.bind(10, forKey: .age)
component.printProfile() // name=ChildComponent, age=10

Frequently Asked Questions

Q. How are singleton values defined per component?

Currently, there is no official method due to some challenges. However, it is easy to implement individually. Define the following class:

import Foundation

public final class SingletonStorage: @unchecked Sendable {
    private let lock = NSRecursiveLock()
    private var storage: [Int: Any] = [:]
    public init() {}
    public func callAsFunction<T>(key: Int = #line, _ make: @autoclosure () -> T) -> T {
        lock.lock()
        defer { lock.unlock() }
        if let value = storage[key] {
            return value as! T
        }
        let value = make()
        storage[key] = value
        return value
    }
}

Then, you can easily use singletons by put this as a property of your Component.

@Component
struct SceneComponent {
    private let singleton = SingletonStorage()

    @Provides(.urlSession)
    var urlSession: URLSession {
        singleton(URLSession())
    }
}

Q. I get a warning 'Conformance to 'Sendable' must occur ... for retroactive conformance'

This is a bug in the Swift compiler. It has been fixed but not yet released. As a workaround, explicitly add Sendable to your component.

Q. I want to suppress the warning to use get

You can avoid the warning by wrapping the value in a tuple.

struct RootComponent: Sendable {
    init() {
        self.logger = Logger(label: "app")
        self.awsClient = AWSClient(
            logger: (logger) // avoid warning
        )
        initContainer(parent: self)
    }

    @Provides(.logger)
    let logger: Logger
    ...
}