【问题标题】:Does Combine have publishers that only publish when a value actually changes?Combine 是否有仅在值实际更改时才发布的发布者?
【发布时间】:2021-04-30 11:53:56
【问题描述】:

有没有办法让@Published 变量只在新值与旧值不同时才发布其值?

现在如果我们有

@Published var test: Bool = false

我们做到了

test = false
test = false
test = false

发布者被调用了 3 次。这很烦人,因为它有时会导致我的 SwiftUI 视图被重新创建,因为在层次结构中较高的某个位置,发布者被设置为之前设置的值,通过另一个被触发的发布者(并且这会破坏文本字段输入,因为视图模型通过发生这种情况时会重新创建层次结构)。

有没有办法只在从falsetrue 或反之亦然时发布?

一个示例场景:

我们在我们的应用程序中创建了一个用户对象,我们想为用户添加一辆车。如果没有找到汽车,应用程序应立即显示“添加汽车”视图,否则显示主应用程序视图。为此,我们在某个地方有一个听众。在我们的顶层视图中,我们有:


    @ObservedObject var viewModel = UserViewModel()


    var body: some View {
            if !viewModel.hasVehicles {
                return AnyView(AddVehicleView(viewModel:AddVehicleViewModel())
            } else {
                return AnyView(UserMainView(user: user))
            }
        }
    }

在我们的UserViewModel 我们有

class UserViewModel: ObservableObject {
    @Published var hasVehicles: Bool = false

  • 某些代码会在某些侦听器触发时更新布尔值。

AddVehicleView 内部,我们有一个表单,允许用户填写一些文本字段并保存车辆。

现在想象一下,由于某种原因,更新 hasVehicles 属性的代码被触发,但仍然没有车辆。会发生什么:

hasVehicles = false

顶层视图被重新评估,导致return AnyView(AddVehicleView(viewModel:AddVehicleViewModel())被执行,我的带有文本字段的表单被清空。

我想在这种情况下,我可以通过将 AddVehicleViewModel() 作为属性放入 View 结构中来解决它,但是在我们希望它被执行多次的情况下,这不会解决它,因为那样会意味着下次构建视图时,它将显示我们上次创建该视图的数据,因为我们重用了视图模型。

【问题讨论】:

  • 你不应该担心@Published 发布重复,它会触发`View` 被重新评估但不一定重绘——这就是差异魔法发生的地方。不幸的是,您正在使用AnyView 抛出所有差异。最好的解决方案是不使用AnyView,让SwiftUI 完成它的工作。
  • 那么你的意思是,一旦你开始使用 AnyView,你就会重绘,因为 SwiftUI 认为它是一个新视图?
  • 如果是这种情况,您如何在返回 2 种不同视图类型的视图中进行 switch/if-else,而不使用 AnyView?
  • 你支持 iOS 13 吗?
  • 如果您将整个开关包装在 Group 中 - 您应该能够删除 AnyView

标签: swiftui combine


【解决方案1】:

在更新属性的代码中使用 Combine 的 removeDuplicates() 运算符。例如,视图模型可以创建一个发布者,它根据其他两个布尔属性的值更新一个布尔属性:

    @Published var hasTrucks: Bool
    @Published var hasCars: Bool
    @Published var hasVehicles: Bool

    func createVehiclePublisher() {
        Publishers.CombineLatest($hasTrucks, $hasCars)
            .map { $0 || $1 }
            .removeDuplicates()
            .assign(to: &$hasVehicles)
    }

使用removeDuplicates() 会导致hasVehicles 仅在其值发生变化时更新。

【讨论】:

    【解决方案2】:

    尝试将常规属性与手动发布者激活一起使用,例如

    class UserViewModel: ObservableObject {
        var hasVehicles: Bool = false {
            willSet {
                if hasVehicles != newValue {
                    objectWillChange.send()
                }
            }
        }
    // ... other code
    }
    

    【讨论】:

    • 这似乎是一个可行的解决方案,但我无法想象我是唯一一个在使用 SwiftUI 构建界面的方式时遇到此类问题的人,一旦你有多层视图管理屏幕?我想知道我是否在某个地方遗漏了什么。如果控制视图的状态未更改 IMO 值,则不应重新生成视图。
    • @swiftPunk,这不是关于任意的副作用代码,而是关于 ObservableObject 概念使用的正确性,如所述的发布者应该是objectWillChange,即。 对象改变之前。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-21
    • 1970-01-01
    • 2021-05-25
    • 1970-01-01
    • 2017-02-06
    相关资源
    最近更新 更多