Examples

Code that this repository compiles.

Each target in Examples uses the local Skein checkout. These blocks are read from the source files at build time, not copied into the site by hand.

BasicUsage

Owned SkeinApplication, lazy singletons, factories, assisted factories, and a typed scope.

Run: swift run --package-path Examples BasicUsage

Open source file ↗
Examples/Sources/BasicUsage/main.swift
import Skein

let applicationModule = module {
    // Created lazily on the first resolution, then reused.
    single(Logger.self, using: Logger.init)

    // Created again for each resolution.
    factory(RequestHandler.self, using: RequestHandler.init)

    // Assisted factories accept one strongly typed runtime value.
    factory(RequestPresenter.self, arguments: RequestPath.self, using: RequestPresenter.init)

    // Scoped values are shared only by one typed scope instance.
    scoped(RequestCache.self, scope: RequestScope.self, provider: { _ in RequestCache() })
}

do {
    let application = try SkeinApplication {
        applicationModule
    }

    let firstLogger: Logger = try application.get()
    let secondLogger: Logger = try application.get()
    print("Singleton reused: \(firstLogger === secondLogger)")

    let firstHandler: RequestHandler = try application.get()
    let secondHandler: RequestHandler = try application.get()
    print("Factory creates new values: \(firstHandler !== secondHandler)")
    print("Nested singleton reused: \(firstHandler.logger === secondHandler.logger)")

    firstHandler.handle("/examples")

    let presenter: RequestPresenter = try application.get(
        arguments: RequestPath(value: "/examples")
    )
    print("Assisted path: \(presenter.path.value)")

    let scope = try application.createScope(RequestScope.self, id: "example")
    let firstCache: RequestCache = try scope.get()
    let secondCache: RequestCache = try scope.get()
    print("Scoped cache reused: \(firstCache === secondCache)")
} catch {
    print("Skein setup or resolution failed: \(error)")
}

ModularComposition

Feature modules and a protocol registration for a live API client.

Run: swift run --package-path Examples ModularComposition

Open source file ↗
Examples/Sources/ModularComposition/main.swift
import Skein

let networkingModule = module {
    single((any APIClient).self, provider: { _ in LiveAPIClient() })
}

let dataModule = module {
    single(UserRepository.self, using: UserRepository.init)
}

let featureModule = module {
    factory(ProfilePresenter.self, using: ProfilePresenter.init)
}

do {
    try startSkein {
        networkingModule
        dataModule
        featureModule
    }
    defer { stopSkein() }

    let presenter: ProfilePresenter = try get()
    print(presenter.title())
} catch {
    print("Skein setup or resolution failed: \(error)")
}

QualifiedBindings

Multiple values of the same type, separated by qualifiers.

Run: swift run --package-path Examples QualifiedBindings

Open source file ↗
Examples/Sources/QualifiedBindings/main.swift
import Skein

let endpointModule = module {
    single(ServiceEndpoint.self, qualifier: ServiceEnvironment.production, provider: { _ in
        ServiceEndpoint(baseURL: "https://api.example.com")
    })
    single(ServiceEndpoint.self, qualifier: ServiceEnvironment.staging, provider: { _ in
        ServiceEndpoint(baseURL: "https://staging.example.com")
    })
}

do {
    try startSkein {
        endpointModule
    }
    defer { stopSkein() }

    let production = try get(
        ServiceEndpoint.self,
        qualifier: ServiceEnvironment.production
    )
    let staging = try get(
        ServiceEndpoint.self,
        qualifier: ServiceEnvironment.staging
    )

    print("Production: \(production.baseURL)")
    print("Staging: \(staging.baseURL)")
} catch {
    print("Skein setup or resolution failed: \(error)")
}

ErrorHandling

Provider failures wrapped in SkeinResolutionError and retried singletons.

Run: swift run --package-path Examples ErrorHandling

Open source file ↗
Examples/Sources/ErrorHandling/main.swift
import Skein

let attempts = Attempts()
let retryModule = module {
    single(String.self, provider: { _ in
        attempts.count += 1
        guard attempts.count > 1 else {
            throw ConnectionError.unavailable
        }
        return "connected"
    })
}

do {
    let _: Int = try get()
} catch SkeinError.notStarted {
    print("Resolution requires an active container")
} catch {
    print("Unexpected pre-start error: \(error)")
}

do {
    try startSkein {
        retryModule
    }
    defer { stopSkein() }

    do {
        let _: String = try get()
    } catch let error as SkeinResolutionError {
        if error.underlying is ConnectionError {
            print("Provider error retained as the diagnostic underlying error")
        }
    }

    // A failed singleton is not cached, so its provider is tried again.
    let connection: String = try get()
    print("Second attempt: \(connection)")

    do {
        let _: Int = try get()
    } catch let error as SkeinResolutionError {
        if case let SkeinError.missingBinding(type, qualifier) = error.underlying {
            print("Missing binding for \(type), qualifier: \(qualifier ?? "none")")
        }
    }
} catch {
    print("Skein setup or resolution failed: \(error)")
}

MainActorValidation

MainActor bindings, eager roots, and async startup validation.

Run: swift run --package-path Examples MainActorValidation

Open source file ↗
Examples/Sources/MainActorValidation/MainActorValidation.swift
import Skein

@main
@MainActor private struct MainActorValidation {
    private static let applicationModule = module {
        instance(AppConfiguration())
        single(AccountScreenModel.self, using: AccountScreenModel.init)
            .root(.eager)
    }

    static func main() async {
        do {
            // The application owns this policy and may log or fail fast here.
            try await startSkein(validation: .declaredRoots) {
                applicationModule
            }
            defer { stopSkein() }

            let screenModel: AccountScreenModel = try get()
            print("Validated main-actor screen model for \(screenModel.apiBaseURL)")
        } catch {
            print("Application startup validation failed: \(error)")
        }
    }
}

TestingExampleTests

A production module replaced with a hand-written fake in an isolated test container.

Run: swift test --package-path Examples

Open source file ↗
Examples/Tests/TestingExampleTests/TestingExampleTests.swift
import Skein
import XCTest

private protocol GreetingService {
    func greeting() -> String
}

private final class LiveGreetingService: GreetingService {
    func greeting() -> String {
        "Hello from production"
    }
}

private final class FakeGreetingService: GreetingService {
    func greeting() -> String {
        "Hello from a test"
    }
}

private final class WelcomeMessage {
    private let service: any GreetingService

    init(service: any GreetingService) {
        self.service = service
    }

    var text: String {
        service.greeting()
    }
}

@MainActor private func makeProductionModule() -> Module {
    module {
        single((any GreetingService).self, provider: { _ in LiveGreetingService() })
        factory(WelcomeMessage.self, using: WelcomeMessage.init)
    }
}

@MainActor private func makeTestModule() -> Module {
    module {
        single((any GreetingService).self, provider: { _ in FakeGreetingService() })
        factory(WelcomeMessage.self, using: WelcomeMessage.init)
    }
}

@MainActor final class TestingExampleTests: XCTestCase {
    func testProductionGraph() throws {
        let application = try SkeinApplication { makeProductionModule() }
        let message: WelcomeMessage = try application.get()

        XCTAssertEqual(message.text, "Hello from production")
    }

    func testGraphWithHandWrittenFake() throws {
        let application = try SkeinApplication { makeTestModule() }
        let message: WelcomeMessage = try application.get()

        XCTAssertEqual(message.text, "Hello from a test")
    }
}
Browse all examples ↗