MVVMKit 3.1.0

MVVMKit 3.1.0

ag维护。



MVVMKit 3.1.0

  • alfogrillo

“MVVMKit”

基于协议的MVVM实现

Version License Platform

MVVMKit是一个基于协议的库,它定义了一种清晰的方式来在你的iOS应用中采用MVVM软件架构
在经典的MVVM实现之外,MVVMKit还提供了工具,可以将导航逻辑放入符合Coordinator协议的实例。


简介

当您使用MVVMKit时,大部分您的软件类型将属于以下类别之一

模型

您应用程序中实际操作的数据。
模型类型可以是来自Core Data数据库的NSManagedObject或来自Web API的Codable实例。

模型的职责

  • 维护应用程序的状态

UIView

实际的用户界面。
在iOS中视图通常是UIView的子类。

UIView的职责

  • 显示应用程序内容
  • 将用户交互传递给UIViewController

UIViewController

UIViewController的子类。

UIViewController的职责

  • 将用户交互传递给视图模型
  • 将视图模型属性绑定到视图

注意:在iOS中,我们将MVVM模式中的View实体分为两个实体:UIViewUIViewController


视图模型

应用程序场景的“大脑”,包含应用的大部分逻辑。

视图模型的责任

  • 管理模型
  • 以适用于视图的方式呈现模型
  • 通知视图控制器何时更新视图

协调器

负责应用导航的实体。

协调器的责任

  • 决定接下来要显示哪个视图控制器以及如何显示
  • 实例化视图控制器和相关视图模型,并执行适当的依赖注入

图形表示

MVVM

特点

  • ⚙️面向协议
  • 🖼可重用视图支持(单元格,辅助视图)
  • 🔨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

结果应该是下面这样

MVVM

安装

MVVMKit 可以通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile 中

pod 'MVVMKit'

MVVMKit 也可以作为 Swift Package 提供。

示例项目

要运行示例项目,首先克隆仓库,然后在 Example 目录中运行 pod install

作者

alfogrillo

许可证

MVVMKit 在 MIT 许可下可用。有关更多信息,请参阅 LICENSE 文件。