【问题标题】:How to avoid force type casting view model instance in child view controllers - MVVM如何避免在子视图控制器中强制类型转换视图模型实例 - MVVM
【发布时间】:2018-01-30 06:56:06
【问题描述】:

我是 MVVM 的新手,对如何避免在子视图控制器中对视图模型实例进行类型转换有点困惑。为了更好地解释它,我添加了下面的代码。

现在我知道每个 ViewController 都应该有一个视图模型,并且应该在 init 或 awakeFromNib 中进行初始化。所以我将协议声明为

public protocol ViewModelCreatorProtocol : class {
    var viewModel : BaseControllerViewModel! {get}
    func createViewModel()
}

我的BaseControllerViewModel 看起来像

public class BaseControllerViewModel {
    //some variables common to all child class
}

现在我有一个BaseViewController,它确认了这个协议,在我的案例中,所有其他 VC 都将从 BaseViewController 扩展

class BaseViewController : UIViewController, ViewModelCreatorProtocol {
    var viewModel: BaseControllerViewModel!

    lazy var disposeBag : DisposeBag = {
        return DisposeBag()
    }()

    override func awakeFromNib() {
        super.awakeFromNib()
        self.createViewModel()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func createViewModel() {
        viewModel = BaseControllerViewModel()
    }

    func hookupViewModelObservables() {
        //hookup observables
    }
}

如您所见,BaseViewController 为所有 childViewControllers 提供了一些公共属性,例如 disposeBag,并在 awakeFromNib 中调用 createViewModel,并确保即使在加载 ViewController 之前也始终实例化 viewModel。 (现在让我们假设我所有的 viewModel 都使用默认的 init 进行初始化,并且没有传递给它们的参数)

问题:

现在我创建一个 ChildViewController 让我们说AudioPlayerViewController

class AudioPlayerViewController: BaseViewController {
    //override createViewModel of parent to pass my ViewController specific view model

    override func createViewModel() {
        self.viewModel = AudioPlayerViewModel()
    }
}

显然我的AudioPlayerViewModel 是从BaseControllerViewModel 扩展而来的

class AudioPlayerViewModel : BaseControllerViewModel {
    var totalDuration : Int = 0
    var timeObserver : Any? = nil
    //some more variables and logics
}

一切正常,但现在如果我必须在我的AudioPlayerViewController 中访问totalDuration,我必须以身份访问它

(self.viewModel as! AudioPlayerViewModel).totalDuration

这使得我在 ViewController 中的整个代码库都被(self.viewModel as! AudioPlayerViewModel) 填充,我认为这提高了我的代码可读性。

有没有更好的方法来解决这个问题?

【问题讨论】:

  • 我的建议是避免使用基本视图控制器和视图模型。重用基类的一些属性或小方法所获得的最小好处远远超过了增加的复杂性。协议更好,但我不知道为什么需要 ViewModelCreatorProtocol。
  • @mike-taverne :问题是我正在尝试设置规则,即每个视图控制器都应该在加载之前初始化其视图模型。所以我认为协议在这里会做得更好。如果我删除了协议,那么创建视图模型将成为视图控制器的责任,但由于协议,它将强制所有子 VC 创建一个名为视图模型的变量,并提供通过覆盖 createViewModel 来提供自己的视图模型实例的机会。故事板的使用使得视图模型的动态注入变得困难
  • 和基类从 nib 实现中唤醒确保所有子 VC 都有机会使用 awakeFromNib 中的实例初始化他们的 vie 模型
  • 您的视图控制器如何知道在调用 awakeFromNib 时要加载哪个视图模型?我知道它知道什么类型的模型,但是它怎么知道要创建该类型的哪个实例呢?例如,如果您的视图控制器显示正在播放的曲目的持续时间,如果您没有告诉它要播放什么曲目,它怎么可能知道该持续时间?
  • @mike-taverne :我明白你在说什么,你说的是viewModel的空初始化。现在没关系,因为我已经在我的问题中提到过。我打算从情节提要中实例化一个视图控制器,然后访问其视图模型属性并将值分配给视图模型内的所有变量。我知道这不是正确的方法,但现在可行

标签: ios swift mvvm


【解决方案1】:

您是否尝试过使用泛型?所以BaseViewController 将有一个必须继承BaseControllerViewModel 的泛型类型。像这样的:

public class BaseControllerViewModel {
    required public init() {
        // needed to do T() in view model creation.
    }
}

class BaseViewController<T: BaseControllerViewModel> : UIViewController, ViewModelCreatorProtocol {
    var viewModel: T! // this will change based on the type you pass in subclass

    lazy var disposeBag : DisposeBag = {
        return DisposeBag()
    }()

    override func awakeFromNib() {
        super.awakeFromNib()
        self.createViewModel()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func createViewModel() {
        viewModel = T() // instantiate the view model
    }

    func hookupViewModelObservables() {
        //hookup observables
    }
}

例如,具体的类将是:

class ExampleVcViewModel: BaseControllerViewModel {
    var subclassVar: Int?
}

class EampleVc: BaseViewController<ExampleVcViewModel> {
    override func viewDidLoad() {
        super.viewDidLoad()
        print(viewModel.subclassVar) // no need to cast
    }
}

【讨论】:

  • 听起来是一个非常巧妙的解决方案,我会尝试直到 +1
  • 这样更好,但仍会创建一个空模型。那可能的价值是什么?
  • 我可能会将BaseControllerViewModel 更改为协议,但我不会担心空的基本模型;这可能会很好,例如,如果项目增长很多并且需要添加一些基本功能
  • @rico-crescenzio : 我不在我的系统好友那里,我会尝试让你知道
  • 可能你应该改变一些东西,因为你有ViewModelCreatorProtocol ,但主要的想法是这个
【解决方案2】:

从BaseViewController中移除viewModel,每个视图控制器都应该有自己的viewModel,没有别的办法

【讨论】:

  • @lu :感谢您的回复,但是我怎样才能强制所有 VC 拥有一个 viewModel 属性并在 init 中初始化它或从 nib 唤醒?我可以让所有 VC 单独确认协议,但是当 viewModel 被初始化时,我怎样才能引入命令呢?像所有 VC 一样,他们的视图模型应该在 awakeFromNib 中初始化。因为我使用情节提要向 ViewController 注入 viewModel 是一个挑战,所以我选择了基类
【解决方案3】:

对于这种情况,我个人使用另一个返回适当视图模型的属性。所以,它可能是:

var audioPlayerViewModel: AudioPlayerViewModel? {
    return viewModel as? AudioPlayerViewModel
}

func createViewModel() {
    viewModel = AudioPlayerViewModel()
}

或者您可以删除属性中的可选项,因为您知道它将完全是AudioPlayerViewModel。

var audioPlayerViewModel: AudioPlayerViewModel {
    return viewModel as! AudioPlayerViewModel
}

【讨论】:

  • 谢谢 :) 这正是我的想法,想知道这样做是否可以 :) 因此 +1
  • 创建一个空模型有什么价值?
  • 此外,除非您能保证模型是预期的类型,否则强制转换可能会使您的应用程序崩溃。
  • @MikeTaverne,我提到了 vm 类型。
猜你喜欢
  • 2020-12-29
  • 1970-01-01
  • 2017-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-28
  • 1970-01-01
相关资源
最近更新 更多