rockbruno / RouterService

💉Type-safe Navigation/Dependency Injection Framework for Swift
MIT License
332 stars 27 forks source link

Update Features to use Property Wrappers #7

Closed rockbruno closed 4 years ago

rockbruno commented 4 years ago

The ugly boilerplate of this framework like AnyFeature and AnyDependenciesInitializer are a result of the Swift compiler's limitation on generics. In this PR, I tested the creation of features using property wrappers instead of the usual dependencies struct. It worked, and by using Mirror to resolve the properties, I am able to drop the associated type from the Feature and all other ugly type-erasures alongside it :)

Here's how features looked before this change:

private enum ProfileFeature: Feature {
    struct Dependencies {
        let client: HTTPClientProtocol
    }

    static var dependenciesInitializer: AnyDependenciesInitializer {
        return AnyDependenciesInitializer(Dependencies.init)
    }

    static func build(
        dependencies: ProfileFeature.Dependencies,
        fromRoute route: Route?
    ) -> UIViewController {
        return ProfileViewController(dependencies: dependencies)
    }
}

With property wrappers, I can remove all the boilerplate:

private struct ProfileFeature: Feature {

    @Dependency var client: HTTPClientProtocol

    func build(fromRoute route: Route?) -> UIViewController {
        return ProfileViewController(client: client)
    }
}

The Feature became a struct to make use of the compiler's synthesized initializers, and the properties are automatically resolved by RouterService. You don't need to create the annoying dependenciesInitializer type anymore and you don't need to worry about features that have zero or one dependency.

This simplified structure makes it easier to develop and test new features. If you want to test build(), you can do either:

let feature = ProfileFeature()
feature.resolve(withStore: yourCustomStore)
feature.build(...)

or the more useful injection through the init:

let feature = ProfileFeature(client: .init(resolvedValue: myMockClient))
feature.build(...)

The original Dependency protocol was also removed because it was just an alias for AnyObject.

rockbruno commented 4 years ago

@willian-policiano-ifood @bocato @MichaelDouglasCS @alexandre-mestre-ifood I think you might be interested in importing this branch in your companies' forks, what do you think? 🙂

rockbruno commented 4 years ago

Since nobody commented on this, I'll merge it as it is.

bocato commented 4 years ago

I will update it right away! Thanks @rockbruno