测试已测试 | ✗ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布最后发布 | 2017年8月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 Gabriele Trabucco 维护。
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 中使用。
如果您想要更有界限地分离类职责,提供了一个 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()
}
}
这并不优雅,但有时候一些对象被用作持有一些适合实体拥有者的抽象上下文。这项技术用于简化实体拥有者的复杂性,并使其免于进一步发现实体。一个上下文可能很有用的非常简单的例子是表视图控制器(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)")
}
}
pod 'SwiftMVVMPattern'
pod 'SwiftMVVMPattern/Presenter'
pod 'SwiftMVVMPattern/Context'
此项目使用MIT许可证许可 - 有关详细信息,请参阅LICENSE.md文件。