CombineViewModel

使用 Combine 实现 Model-View-ViewModel (MVVM) 模式。

简介

CombineViewModel 的主要目标是使 UIKit 和 AppKit 中的视图更新与 SwiftUI 中一样容易。

在 SwiftUI 中,您编写符合 Combine 的 ObservableObject 协议的模型和视图模型类。SwiftUI

  1. 通过 @ObservedObject 属性包装器观察每个模型的 objectWillChange 发布者;并且
  2. 自动重新渲染视图层次结构的相应部分。

在 SwiftUI 之外使用 objectWillChange 的问题在于,没有内置的方法来实现 (2) —— 被通知对象将要更改与知道对象已经更改并且是时候更新视图是不同的。

ObjectDidChangePublisher

考虑以下用于显示用户社交网络配置文件的视图模型的草图

// ProfileViewModel.swift

import CombineViewModel
import UIKit

class ProfileViewModel: ObservableObject {
  @Published var profileImage: UIImage?
  @Published var topPosts: [Post]

  func refresh() {
    // Request updated profile info from the server.
  }
}

使用 CombineViewModel,您可以使用 observe(on:) 运算符订阅已更改通知

let profile = ProfileViewModel()

profileSubscription = profile.observe(on: DispatchQueue.main).sink { profile in
  // Called on the main queue when either (or both) of `profileImage`
  // or `topPosts` have changed.
}

profile.refresh()

自动视图更新

基于 ObjectDidChangePublisher 构建的是 ViewModelObserver 协议和 @ViewModel 属性包装器。与像上面那样手动管理 ObjectDidChangePublisher 订阅不同,我们可以让它自动管理

// ProfileViewController.swift

import CombineViewModel
import UIKit

// 1️⃣ Conform your view controller to the ViewModelObserver protocol.
class ProfileViewController: UITableViewController, ViewModelObserver {
  enum Section: Int {
    case topPosts
  }

  @IBOutlet private var profileImageView: UIImageView!
  private var dataSource: UITableViewDiffableDataSource<Section, Post>!

  // 2️⃣ Declare your view model using the `@ViewModel` property wrapper.
  @ViewModel private var profile: ProfileViewModel

  // 3️⃣ Initialize your view model in init().
  required init?(profile: ProfileViewModel, coder: NSCoder) {
    super.init(coder: coder)
    self.profile = profile
  }

  // 4️⃣ The `updateView()` method is automatically called on the main queue
  //     when the view model changes. It is always called after `viewDidLoad()`.
  func updateView() {
    profileImageView.image = profile.profileImage

    var snapshot = NSDiffableDataSourceSnapshot<Section, Post>()
    snapshot.appendSections([.topPosts])
    snapshot.appendItems(profile.topPosts)
    dataSource.apply(snapshot)
  }

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    profile.refresh()
  }
}

进一步阅读

Example 目录中,您将找到一个完整的 iOS 示例应用程序,演示如何将 CombineViewModel 集成到您的应用程序中。

安装

CombineViewModel 通过 Swift Package Manager 分发。要将其添加到您的 Xcode 项目,请导航到 File > Add Package Dependency…,粘贴存储库 URL,然后按照提示操作。

Screen capture of Xcode on macOS Big Sur, with the Add Package Dependency menu item highlighted

绑定

CombineViewModel 还提供了互补的 Bindings 模块。它提供了两个运算符——<~输入绑定运算符,和 ~>输出绑定运算符——以及支持它的各种类型和协议。请注意,Bindings 模块提供的“绑定”概念与 SwiftUI 的 Binding 类型 不同。

还提供了特定于平台的绑定帮助程序

贡献

在您的项目中有有用的响应式扩展吗?请考虑将其贡献回社区!

有关更多详细信息,请参阅 CONTRIBUTING 文档。谢谢 贡献者

许可证

CombineViewModel 版权所有 © 2019–20 thoughtbot, inc.。它是自由软件,可以根据 LICENSE 文件中规定的条款进行重新分发。

关于

thoughtbot

CombineViewModel 由 thoughtbot, inc. 维护和资助。thoughtbot 的名称和徽标是 thoughtbot, inc. 的商标。

我们热爱开源软件!请查看我们的其他项目聘请我们来帮助构建您的产品。