SwiftMVVMPattern 0.9.1

SwiftMVVMPattern 0.9.1

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最后发布2017年8月
SwiftSwift 版本3.0
SPM支持 SPM

Gabriele Trabucco 维护。



  • Gabriele Trabucco

MVVMSwiftPattern

MVVMSwiftPattern 是一组协议,允许您的 iOS 应用符合 MVVM 设计模式。

描述

当您开始开发一个新的 iOS 应用时,您可能会使用经典 MVC 方法,并且可能会开始在 ViewController 中编写代码。这将导致您拥有庞大的 ViewController。为了转向 MVVM 设计模式,您应该想象应用由一些 Model 组成,这些是代表您的应用数据的对象,View 是 UIView + UIViewController,而 ViewModel 则是 View 和您的 Application Backend 之间的适配器。应用后端 (AB) 在 Apple 对 MVC 的描述中也没有被恰当地提及,也没有在 MVVM 描述中。AB 是所有应用都具有的,而 iOS 中使用 MVC 在很多情况下只是与 ViewController 代码混合。AB 是非 UI 元素的抽象世界,其中一些类完成了真正的任务,无需有完整的交互式 UI,比如下载/上传文件、运行算法等。AB 对象公开了一些函数,允许您完成一些任务,就是如此。

MVVM 应用的图表表示

 View + ViewController <-> ViewModel <-> AB + Model

入门基础

class CustomView: UIView {}

extension CustomView: View {}

class CustomViewModel: ViewModel {
  var info: String
  ...

  func save() { ... }
}

class ViewController: UIViewController, ViewModelObserver {

  var viewModel: CustomViewModel?

  func bind() {
    // Here you can use an observing technique like KVO, Rx
    // Update the View
  }

  @IBAction func didTapSave(_ sender: Any) {
    viewModel.save()
  }
}

为了使您的项目中 MVVM 清晰,我们可以将其作为一个规则:决不在 ViewModel 类中使用 View/UIKit 类。 Views 属于 ViewController 领域,不允许在 ViewModel 中使用。

额外功能 1:呈现器

如果您想要更有界限地分离类职责,提供了一个 Presenter 基类和一个 Presentable 协议。

这样做,您的 ViewController 需要选择,或者更好的是注入,一个合适的 Presenter 子类和合适的 ViewModel。当适当的时候,ViewController 会调用 presenter.update(view: customView, with: viewModel)

入门 - 呈现器

class CustomViewPresenter : Presenter<CustomView, CustomViewModel> {
  func update(view: CustomView, with viewModel: CustomViewModel) {
    // Here you can use an observing technique like KVO, Rx
    // You could require to keep a strong ref to your ViewModel

    // Update View
  }
}

class ViewController: UIViewController, ViewModelHolder, Presentable {

  @IBOutlet weak var customView: CustomView?  
  var viewModel: CustomViewModel?
  var presenter: CustomViewPresenter?

  override func viewDidLoad() {
    super.viewDidLoad()

    guard let v = customView, let vm = viewModel else {
      return
    }

    presenter.update(v, with: vm)
  }

  @IBAction func didTapSave(_ sender: Any) {
    viewModel.save()
  }
}

额外功能 2:上下文

这并不优雅,但有时候一些对象被用作持有一些适合实体拥有者的抽象上下文。这项技术用于简化实体拥有者的复杂性,并使其免于进一步发现实体。一个上下文可能很有用的非常简单的例子是表视图控制器(TableViewController)。

当TableViewDataSource请求新的值以渲染其TableView时,它启动一个异步过程,这个过程始于发现TableView由多少个单元格组成,并最终异步地请求具体的单元格。由于这个过程在某些罕见情况下是异步的,例如当TableView的刷新率非常高时,用户最终可能会在选择仍在可见的单元格时,DataSource已被无效化,新的单元格即将被渲染。当发生这种情况时,visibleCell的indexPath可能是无效的,或者如果有效,则指向用于单元格渲染的不同实体。正如您可以想象的,我们现在处于一个不可预测的情况。

为了避免这种不便,我们可以创建一个遵守《Contextable》协议的CustomCell并在单元格分发时分配一个Context。这样做的话,在单元格视图有效期间,它将携带用于渲染的上下文,并且这个上下文可以在未来的操作中返回。

请记住,在像RxSwift或ReactiveCocoa这样的函数式编程环境中,这个协议可能完全没有用,因为实体可能被绑定到对象的功能所携带。

开始使用上下文


class CustomCell : UITableViewCell, Contextable {
  var context: Any?
}

class TableViewController: UITableViewController {

  var dataSource: [String]?

  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    guard let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as? CustomCell else {
      fatalError("No Valid Cell")
    }

    cell.context = dataSource[indexPath.row]

    ...

    return cell
  }

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    // Get the still valid cell in the view.
    guard let cell = tableView.cellForRow(at: indexPath) as? CustomCell,
      let entity = cell.context as? String else {
      return
    }

    print("\(entity)")
  }
}

安装

CocoaPod

pod 'SwiftMVVMPattern'

pod 'SwiftMVVMPattern/Presenter'

pod 'SwiftMVVMPattern/Context'

作者

  • Gabriele Trabucco

许可证

此项目使用MIT许可证许可 - 有关详细信息,请参阅LICENSE.md文件。