基于协议的MVVM实现
MVVMKit是一个基于协议的库,它定义了一种清晰的方式来在你的iOS应用中采用MVVM软件架构。
在经典的MVVM实现之外,MVVMKit还提供了工具,可以将导航逻辑放入符合Coordinator
协议的实例。
简介
当您使用MVVMKit时,大部分您的软件类型将属于以下类别之一
模型
您应用程序中实际操作的数据。
模型类型可以是来自Core Data数据库的NSManagedObject
或来自Web API的Codable
实例。
模型的职责
- 维护应用程序的状态
UIView
实际的用户界面。
在iOS中视图通常是UIView
的子类。
UIView的职责
- 显示应用程序内容
- 将用户交互传递给
UIViewController
UIViewController
UIViewController
的子类。
UIViewController的职责
- 将用户交互传递给视图模型
- 将视图模型属性绑定到视图
注意:在iOS中,我们将MVVM模式中的View
实体分为两个实体:UIView
和UIViewController
。
视图模型
应用程序场景的“大脑”,包含应用的大部分逻辑。
视图模型的责任
- 管理模型
- 以适用于视图的方式呈现模型
- 通知视图控制器何时更新视图
协调器
负责应用导航的实体。
协调器的责任
- 决定接下来要显示哪个视图控制器以及如何显示
- 实例化视图控制器和相关视图模型,并执行适当的依赖注入
图形表示
特点
-
⚙️ 面向协议 -
🖼 可重用视图支持(单元格,辅助视图) -
🔨 Diffable Data Source ready -
📌 Combine bindings -
🧩 嵌入式视图 - 👆🏼 自定义单元格交互
-
🧭 协调器支持
代码示例
协调导航
视图控制器将用户交互转发给视图模型。
class RootViewController: UIViewController, ViewModelOwner {
typealias ViewModelType = RootViewModel
var viewModel: RootViewModel!
func bind(viewModel: RootViewModel) { … }
@IBAction func didTapButton(_ sender: UIButton) {
viewModel.didTapButton()
}
}
视图模型使用协调器开始导航。
class RootViewModel: CoordinatedViewModel {
typealias CoordinatorType = RootCoordinator
let coordinator: RootCoordinator
init(coordinator: RootCoordinator) { … }
func didTapButton() {
coordinator.showDestinationViewController()
}
协调器实例化并将在目标场景中的适当依赖注入。
class RootCoordinator: Coordinator {
let weakViewController: WeakReference<UIViewController>
init(sourceViewController viewController: UIViewController) { … }
func showDestinationViewController() {
let viewController = SomeViewController()
viewController.viewModel = SomeViewModel()
self.viewController?.show(viewController, sender: nil)
}
可变数据源
定义单元格及其视图模型。
struct MyCellViewModel: ReusableViewViewModel {
let identifier: String = MyCell.identifier
let text: String?
}
class MyCell: UICollectionViewCell, CustomBinder {
typealias ViewModelType = MyCellViewModel
@IBOutlet private var label: UILabel!
func bind(viewModel: ViewModelType) {
label.text = viewModel.text
}
}
使场景的视图模型符合 DiffableCollectionViewViewModel
。
此外,添加发布数据源快照的逻辑。
class MyViewModel: DiffableCollectionViewViewModel {
typealias SectionType = Section
private let state: CurrentValueSubject<[MySectionModel], Never> = .init([])
enum Section {
case main
case second
}
var snapshot: AnyPublisher<SnapshotAdapter, Never> {
state
.map(createSnapshot(from:))
.eraseToAnyPublisher()
}
func createSnapshot(from: [MySectionModel]) -> SnapshotAdapter {
// create your snapshot here
.init()
}
}
从 MVVMDiffableCollectionViewController
派生。
class MyViewController: MVVMDiffableCollectionViewController<MyViewModel> { }
嵌入视图控制器
使视图控制器符合 ContainerViewProvider
。
class ContainerViewController: UIViewController, ViewModelOwner, ContainerViewProvider {
typealias ViewModelType = ContainerViewModel
@IBOutlet weak private var containerView: UIView!
var viewModel: ContainerViewModel!
func view(for kind: ContainerViewKind) -> UIView? {
switch kind {
case .main:
return containerView
}
}
}
使协调器符合 EmbedderCoordinator
。
final class ContainerCoordinator: EmbedderCoordinator {
typealias ViewController = ContainerViewController
typealias ContainerViewKind = ViewController.ContainerViewKind
func embedChildViewController(in view: ContainerViewKind) {
let childViewController = ChildViewController()
guard let containerView = viewController?.view(for: view) else {
return
}
// embed here ‘childViewController’ into the ‘containerView’
}
在视图模型中开始嵌入过程,指定所需的视图标识符。
final class ContainerViewModel: CoordinatedViewModel {
typealias Coordinator = ContainerCoordinator
let coordinator: ContainerCoordinator
func didTapSomething() {
coordinator.embedChildViewController(in: .main)
}
自定义单元格交互
使单元格的视图模型符合 ReusableViewViewModel
。
struct SampleCellViewModel: ReusableViewViewModel {
let identifier: String = SampleCell.identifier
let text: String?
}
为您的单元格自定义交互创建一个代理,并将单元格符合 CustomDelegator
协议。
此外,还需要使单元格符合 CustomBinder
,在该绑定中,执行通常的视图模型属性绑定。
protocol SampleCellDelegate: class {
func didTapButton(in sampleCell: SampleCell)
}
class SampleCell: UICollectionViewCell, CustomBinder, CustomDelegator {
typealias ViewModelType = SampleCellViewModel
typealias Delegate = SampleCellDelegate
@IBOutlet private weak var titleLabel: UILabel!
func bind(viewModel: SampleCellViewModel) {
titleLabel.text = viewModel.text
}
@IBAction func didTapButton(_ sender: UIButton) {
delegate?.didTapButton(in: self)
}
}
使场景的视图控制器符合单元格的代理协议以接收事件。
extension SampleViewController: SampleCellDelegate {
func didTapButton(in sampleCell: SampleCell) {
guard let indexPath = collectionView.indexPath(for: sampleCell) else { return }
viewModel.didTapButton(at: indexPath)
}
}
模板
将 Templates/MVVMKit
文件夹复制到 ~/Library/Developer/Xcode/Templates
。
结果应该是下面这样
安装
MVVMKit 可以通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile 中
pod 'MVVMKit'
MVVMKit 也可以作为 Swift Package 提供。
示例项目
要运行示例项目,首先克隆仓库,然后在 Example
目录中运行 pod install
作者
许可证
MVVMKit 在 MIT 许可下可用。有关更多信息,请参阅 LICENSE 文件。