在 SwiftUI 模型对象中发布的计算属性

erd*_*ser 3 swift swiftui combine

假设我的 SwiftUI 应用程序中有一个如下所示的数据模型:

class Tallies: Identifiable, ObservableObject {
  let id = UUID()
  @Published var count = 0
}

class GroupOfTallies: Identifiable, ObservableObject {
  let id = UUID()
  @Published var elements: [Tallies] = []
}
Run Code Online (Sandbox Code Playgroud)

我想添加一个GroupOfTallies类似于以下内容的计算属性:

// Returns the sum of counts of all elements in the group
var cumulativeCount: Int {
  return elements.reduce(0) { $0 + $1.count }
}
Run Code Online (Sandbox Code Playgroud)

但是,我希望 SwiftUI 在cumulativeCount更改时更新视图。当elements更改(数组获得或丢失元素)或count任何包含的Tallies对象的字段更改时,都会发生这种情况。

我曾考虑将其表示为AnyPublisher,但我认为我对 Combine 的掌握程度不够好,无法使其正常工作。这个答案中提到了这一点,但是从它创建的 AnyPublisher 基于已发布Double而不是已发布Array。如果我尝试不加修改地使用相同的方法,则cumulativeCount只会在元素数组更改时更新,而不会在count元素之一的属性更改时更新。

New*_*Dev 5

这里有多个问题需要解决。

首先,重要的是要了解 SwiftUI 在检测到更改时更新视图的主体,无论是在@State属性中,还是来自ObservableObject(通过@ObservedObject@EnvironmentObject属性包装器)。

在后一种情况下,这是通过@Published属性或手动使用objectWillChange.send(). objectWillChangeObservableObjectPublisher可在任何ObservableObject.

这是一个很长的说法,如果计算属性的更改与任何属性的更改一起引起@Published- 例如,当从某处添加另一个元素时:

elements.append(Talies())
Run Code Online (Sandbox Code Playgroud)

那么不需要做任何其他事情- SwiftUI 将重新计算观察它的视图,并将读取计算属性的新值cumulativeCount


当然,如果.count其中一个Tallies对象的属性发生变化,这不会导致 发生变化elements,因为Tallies是引用类型。

给出您的简化示例的最佳方法实际上是使其成为值类型 - a struct

struct Tallies: Identifiable {
  let id = UUID()
  var count = 0
}
Run Code Online (Sandbox Code Playgroud)

现在,任何Tallies对象的更改都会导致 的更改elements,这将导致“观察”它的视图获得计算属性的新值。同样,不需要额外的工作。


但是,如果您坚持认为Tallies无论出于何种原因都不能是值类型,那么您需要Tallies通过订阅其.objectWillChange发布者来听取任何更改:

class GroupOfTallies: Identifiable, ObservableObject {
   let id = UUID()
   @Published var elements: [Tallies] = [] {
      didSet {
         cancellables = [] // cancel the previous subscription
         elements.publisher
            .flatMap { $0.objectWillChange }
            .sink(receiveValue: self.objectWillChange.send) 
            .store(in: &cancellables)
      }
   }

   private var cancellables = Set<AnyCancellable>

   var cumulativeCount: Int {
     return elements.reduce(0) { $0 + $1.count } // no changes here
   }
} 
Run Code Online (Sandbox Code Playgroud)

以上将通过以下方式订阅elements数组中的更改(以考虑添加和删除):

  • 将数组转换Sequence为每个数组元素的发布者
  • 然后再次 flatMap 每个数组元素,这是一个Tallies对象,到它的objectWillChange发布者
  • 然后对于任何输出,调用objectWillChange.send(),以通知观察它自己更改的视图。