Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments

Part 3

Introduction

Some objects need parameters only available at runtime. For example, a UserService might require a userId that’s known only after a user logs in. To handle such cases we should support dynamic injection when resolving in…


This content originally appeared on DEV Community and was authored by Hugo Granja

Part 3

Introduction

Some objects need parameters only available at runtime. For example, a UserService might require a userId that's known only after a user logs in. To handle such cases we should support dynamic injection when resolving instances.

Registration

Using Swift's parameter packs, we can let our factory closures specify any arguments needed when registering services.

final class Service<T, each A> {
    let lifetime: Lifetime
    let factory: (Container, repeat each A) -> T

    init(
        _ lifetime: Lifetime,
        _ factory: @escaping (Container, repeat each A) -> T
    ) {
        self.lifetime = lifetime
        self.factory = factory
    }
}

public func register<T, each A>(
    _ type: T.Type,
    lifetime: Lifetime = .transient,
    _ factory: @escaping (Container, repeat each A) -> T
) {
    let key = String(describing: type)
    let service = Service(lifetime, factory)
    services[key] = service
}

Resolution

Upon resolution we expect to be given the same arguments that were previously specified on registration to create an instance of that type.

public func resolve<T, each A>(
    _ type: T.Type,
    arguments: repeat each A
) throws -> T {
    let key = String(describing: type)

    guard
        let service = services[key] as? Service<T, repeat each A>
    else {
        fatalError("[DI] Service for type \(type) not found!")
    }

    switch service.lifetime {
    case .transient:
        return service.factory(self, repeat each arguments)

    ...
    }
}

Usage

Assuming userId is a string, here’s how to register and resolve services:

let container = Container()

container.autoRegister(
    DatabaseService.self,
    using: DatabaseServiceImpl()
)

container.register(UserService.self) { container, userId in
    UserServiceImpl(
        databaseService: container.resolve(DatabaseService.self),
        userId: userId
    )
}

container.resolve(UserService.self, arguments: "1234")

To reduce coupling, classes requiring UserService should not directly reference the container. Instead, inject a closure to create UserService, as demonstrated below with UserJourneyFactoryImpl.

final class UserJourneyFactoryImpl: UserJourneyFactory {
    private let userServiceFactory: (String) -> UserService

    init(
        userServiceFactory: @escaping (String) -> UserService
    ) {
        self.userServiceFactory = userServiceFactoryViewFactory
    }

    func makeUserService(userId: String) -> UserService {
        return userServiceFactory(userId)
    }
}

container.register(UserJourneyFactory.self) { r in
    UserJourneyFactoryImpl(
        userServiceFactory: { userId in
            r.resolve(UserService.self, arguments: userId)
        }
    )
}

This setup allows, for example, a Coordinator to use UserJourneyFactory to create user-specific journeys in the app. Alternatively, you could inject the factory closure directly into the Coordinator.

Explore the code

Check out the full implementation of this library on BackpackDI on GitHub.

For a practical example, see MobileUseCases on GitHub.
I'd recommend looking at the entry point of the app MUCApp.swift and AppContainer.swift inside the DependencyInjection folder for a demonstration on how to centralize the configuration of the container and establish a composition root.

That’s it for this guide for now!

Let me know if you’d like to see other features or examples in the comments.


This content originally appeared on DEV Community and was authored by Hugo Granja


Print Share Comment Cite Upload Translate Updates
APA

Hugo Granja | Sciencx (2024-11-06T18:02:44+00:00) Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments. Retrieved from https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/

MLA
" » Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments." Hugo Granja | Sciencx - Wednesday November 6, 2024, https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/
HARVARD
Hugo Granja | Sciencx Wednesday November 6, 2024 » Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments., viewed ,<https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/>
VANCOUVER
Hugo Granja | Sciencx - » Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/
CHICAGO
" » Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments." Hugo Granja | Sciencx - Accessed . https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/
IEEE
" » Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments." Hugo Granja | Sciencx [Online]. Available: https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/. [Accessed: ]
rf:citation
» Creating a simple dependency injection framework in Swift [Part 4]: Dynamic arguments | Hugo Granja | Sciencx | https://www.scien.cx/2024/11/06/creating-a-simple-dependency-injection-framework-in-swift-part-4-dynamic-arguments/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.