首页 > 解决方案 > 使用泛型将额外的道具注入视图模型

问题描述

这篇博文之后,我在我的应用程序中创建了一个 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
        }
    }
}

视图控制器

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
    }
}

视图模型

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在配置文件场景的情况下。

这种模式使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?我所有不接受任何东西的模型感觉不舒服

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

标签: swiftmvvmrx-swift

解决方案


与其介绍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
    }
}

推荐阅读