使用泛型将额外的道具注入视图模型

Injecting additional props into a viewmodel using generics

this blog post 之后,我在我的应用程序中创建了一个 MVVM 模式。

我有以下 -

protocol ViewModelType {
    associatedtype Dependency
    associatedtype Bindings

    init(dependency: Dependency, bindings: Bindings)
}

enum Attachable<VM: ViewModelType> {

    case detached(VM.Dependency)
    case attached(VM.Dependency, VM)

    mutating func bind(_ bindings: VM.Bindings) -> VM {
        switch self {
        case let .detached(dependency):
            let vm = VM(dependency: dependency, bindings: bindings)
            self = .attached(dependency, vm)
            return vm
        case let .attached(dependency, _):
            let vm = VM(dependency: dependency, bindings: bindings)
            self = .attached(dependency, vm)
            return vm
        }
    }
}

ViewController

final class StartViewController: BaseViewController<StartView> {

    var viewModel: Attachable<StartViewModel>!
    var bindings: StartViewModel.Bindings {

        let viewWillAppear = rx.sentMessage(#selector(UIViewController.viewWillAppear))
            .mapToVoid()
            .asDriverOnErrorJustComplete()

        return StartViewModel.Bindings(
            checkAuthState: viewWillAppear
        )
    }
}

extension StartViewController: ViewModelAttaching {
    func bind(viewModel: StartViewModel) -> StartViewModel {
        return viewModel
    }
}

ViewModel

final class StartViewModel: ViewModelType {

    typealias Dependency = HasAuthService

    let authCheck: Driver<Void>

    struct Bindings {
        let checkAuthState: Driver<Void>
    }

    private let disposeBag = DisposeBag()

    init(dependency: Dependency, bindings: Bindings) {

        authCheck = bindings.checkAuthState
            .flatMap {
                return dependency.authSvc.checkSession()
                .mapToVoid()
                .asDriver(onErrorJustReturn: ())
        }
    }
}

这使我可以按如下方式设置我的视图 -

        let viewController = StartViewController()
        let avm: Attachable<StartViewModel> = .detached(dependencies)
        let viewModel = viewController.attach(wrapper: avm)

        navigationController.setViewControllers([viewController], animated: false)

这很好用,很容易测试,我对它的工作方式很满意。然而,我有一个场景,我需要将一些额外的道具传递到我的 ViewModel 中。

比如一个userId在profile场景的情况下

这个模式让 init 方法远离所以我需要一种方法来通过 .detached(dependencies)

注入额外的道具

类似于 .detached(dependencies, userId) 但是我不确定如何以通用方式执行此操作。

我试过了 - 注意添加了 Propsprops


protocol ViewModelType {
    associatedtype Dependency
    associatedtype Bindings
    associatedtype Props

    init(dependency: Dependency, bindings: Bindings, props: Props?)
}

enum Attachable<VM: ViewModelType> {

    case detached(VM.Dependency, VM.Props)
    case attached(VM.Dependency, VM, VM.Props)

    mutating func bind(_ bindings: VM.Bindings) -> VM {
        switch self {
        case let .detached(dependency, props):
            let vm = VM(dependency: dependency, bindings: bindings, props: props)
            self = .attached(dependency, vm, props)
            return vm
        case let .attached(dependency, _, props):
            let vm = VM(dependency: dependency, bindings: bindings, props: props)
            self = .attached(dependency, vm, props)
            return vm
        }
    }
}

然而,这需要我用以下内容更新我的 ViewModel -

**ViewModel**
```swift
typealias Props = <Some Prop Type>
init(dependency: Dependency, bindings: Bindings, props: Props?)

这很好用,除非我不需要注入任何道具。然后我需要为未使用的东西指定类型。

我试过了 -

typealias Props = Void?
init(dependency: Dependency, bindings: Bindings, props: Props?)

然后使用 -

设置我的视图

let avm: Attachable<StartViewModel> = .detached(dependencies, nil)

但是不得不在我所有不接受任何东西的模型中添加 typealias Props = Void? 感觉不对

我怎样才能以最可扩展、最通用的方式实现这一点?

为什么不介绍 Props 而不是隐藏 Dependency 后面的附加信息? 因此,例如,在您的情况下,您可以:

final class ProfileViewModel: ViewModelType {

    let authCheck: Driver<Void>

    struct Dependency {
        let authSvc: HasAuthService
        let userId: String
    }

    struct Bindings {
        let checkAuthState: Driver<Void>
    }

    private let disposeBag = DisposeBag()

    init(dependency: Dependency, bindings: Bindings) {

        authCheck = bindings.checkAuthState
            .flatMap {
                return dependency.authSvc.checkSession()
                .mapToVoid()
                .asDriver(onErrorJustReturn: ())
        }

        // use dependency.userId
    }
}