首页 > 解决方案 > SwiftUI 和组合

问题描述

我正在关注Firebase YouTube 频道上的视频。大约从 27:45 开始,讲师尝试根据布尔值设置一个变量,并在以下代码中结束init(task: Task)

$task
    .map { task in
        task.isCompleted ? "checkmark.circle.fill" : "circle"
    }
    .assign(to: \.completionStateIconName, on: self)
    .store(in: &cancellables)

这对我来说似乎过于复杂。首先,我找不到关于.map在结构对象上使用的文档,只能在数组等上使用。其次,这&cancellables东西是怎么回事?(它的定义与.private var cancellables = Set<AnyCancellable>()之前一样init{}。)第三,为什么所有这些代码,而不仅仅是:

task.completionStateIconName = task.isCompleted ? "checkmark.circle.fill" : "circle"

这似乎给出了相同的结果,但是第一个代码片段有效,而第二个无效?

标签: swiftuicombine

解决方案


$task(带有$前缀)是@Published属性包装器的投影值,它返回类型为 的变量Published.Publisher。换句话说,它是一个组合发布者,它在属性(在这种情况下Task)发生变化时发布一个值。

如果您没有了解 Combine 框架(或其他响应式框架),那么这个答案肯定是不够的。在高层次上,Combine 发布者发出值,您可以通过诸如 之类的运算符对其进行转换.map,并最终订阅,例如使用.sinkor .assign

所以,逐行:

// a publisher of Task values
$task 
  // next, transform Task into a String using its isCompleted property
  .map { task in
     task.isCompleted ? "circle.fill" : "circle"
  }
  // subscribe, by assigning the String value to the completionStateIconName prop
  .assign(to: \.completionStateIconName, on: self)

现在,上面返回一个 的实例AnyCancellable,当您想要接收值时需要保留它。因此,您要么需要将其直接存储为属性,要么使用.store 将其添加到Set<AnyCancellable>一个常见的方法中。


那么,为什么会如此复杂呢?大概是这样构建的,以便如果task属性发生变化,Combine 管道将更新该completionStateIconName属性。

如果你刚刚这样做:

completionStateIconName = task.isCompleted ? "circle.fill" : "circle"

这将在开始时分配值。


话虽如此,在这种特殊情况下,使用 Combine 实际上可能过于复杂,而仅使用didSet

var task: Task {
   didSet {
      completionStateIconName ? task.isCompleted ? "circle.fill" : "circle"
   }
}

推荐阅读