【问题标题】:onReceive String.publisher lead to infinite looponReceive String.publisher 导致无限循环
【发布时间】:2020-03-10 12:42:40
【问题描述】:

我在 View 中使用了两个发布者:

A:String.publisher

B:ObservableObject 包含一个@Published String 类型

如果我监控发布者 A,我会得到一个无限循环。 但是监控发布者B是可以的!

    import SwiftUI
    import Combine

    class Model: ObservableObject{
        @Published var someBool = false
        @Published var name:String = ""
    }

    struct ContentView: View {
        // Publisher A
        @State var name = ""
        // Publisher B
        @ObservedObject var model = Model()

        var body: some View {
            VStack {
                // Plan A: lead to infinite loop!!!
                TextField("Input Name", text: $name)
                // Plan B: It's OK
                //TextField("Input Name", text: $model.name)

                .onReceive(name.publisher.reduce("", {t,c in
                    t + String(c)
                })) {text in
                    print("change to \(text)")
                    self.model.someBool.toggle()    //Plan A: infinite loop!!!
                }
                /*
                .onReceive(model.$name){name in
                    print("change to \(name)")
                    self.model.someBool.toggle()    //Plan B: It's OK!!!
                }
                */
            }
        }
    }

虽然我在onReceive()中改变了model.someBool的值,但是Plan B没问题,Plan A导致死循环。 这是为什么???谢谢:)

【问题讨论】:

  • 从 "A" 中删除 self.model.someBool.toggle() 它将按预期工作。
  • 我知道它成功了,但是为什么 Play B 也可以在没有删除 self.model.someBool.toggle() 的情况下工作???
  • @ObservedObject 在“B”中只通知 SwiftUI 一次,在“A”中,您有两个不同的“更新请求”,首先是因为状态变量发生了变化(通过输入文本字段),其次是模型修改.形成一个循环。
  • 你的模型对状态作出反应,状态对模型作出反应,模型对状态作出反应.....
  • 我也在为 .onReceive 的无限循环问题而苦苦挣扎。我可以使用上面作者的代码轻松复制无限循环——简单的复制粘贴到新的 SwiftUI 项目中,然后单击运行。问题是为什么编译后的代码会立即触发无限循环,甚至在用户在 TextField 中键入单个值之前

标签: swift swiftui combine


【解决方案1】:

希望,你需要一个真实的来源。如果您不喜欢使用您的模型,具有 State / Binding 对的等效代码可能如下所示

struct ContentView: View {
    @State var name: String = ""
    @State var flag = false
    var body: some View {
        let subject = CurrentValueSubject<String, Never>(name)
        return VStack {
            TextField("Input Name", text: $name).textFieldStyle(RoundedBorderTextFieldStyle()).padding()
            .onReceive(subject) { name in
                print("change to \(name)")
                self.flag.toggle() // toggle every char typing
            }
        }
    }
}

在您的示例中,我禁用(参见注释行)模型中的默认“请求”

import SwiftUI
import Combine

class Model: ObservableObject{
    var someBool = false {
        willSet {
            print("will change to", newValue)
            print("ask SwiftUI to update from model")
            //self.objectWillChange.send()
        }
        didSet {
            print(oldValue, "changed")
        }
    }
}

struct ContentView: View {
    @State var name = ""
    @StateObject var model = Model()

    var body: some View {
        VStack {
            TextField("Input Name", text: $name).textFieldStyle(RoundedBorderTextFieldStyle())

            .onReceive(name.publisher.reduce("", {t,c in
                t + String(c)
            })) {text in
                print("change to \(text)")
                self.model.someBool.toggle()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

打字时会打印出来

true changed
change to Qw
will change to true
ask SwiftUI to update from model
false changed
change to Qwe
will change to false
ask SwiftUI to update from model
true changed
change to Qwer
will change to true
ask SwiftUI to update from model
false changed
change to Qwert
will change to false
ask SwiftUI to update from model
true changed
...

现在取消注释模型中的行

class Model: ObservableObject{
    var someBool = false {
        willSet {
            print("will change to", newValue)
            print("ask SwiftUI to update from model")
            self.objectWillChange.send()
        }
        didSet {
            print(oldValue, "changed")
        }
    }
}

然后再次运行它... 它将无限循环打印

...
change to 
will change to true
ask SwiftUI to update from model
false changed
change to 
will change to false
ask SwiftUI to update from model
true changed
change to 
will change to true
ask SwiftUI to update from model
false changed
change to 
will change to false
ask SwiftUI to update from model
true changed
change to 
...

您的模型发生了变化,SwiftUI 正在重新评估它的主体,因此模型再次发生变化......在一个循环中。

最小循环示例

import SwiftUI
import Combine

class Model: ObservableObject {
    @Published var flag = false
}
struct ContentView: View {
    @StateObject var model = Model()
    var body: some View {
            Color.yellow
                .onReceive(model.$flag) {_ in
                    print(".")
                self.model.flag.toggle()
            }
    }
}

【讨论】:

  • 感谢最小循环示例。但是 1) 是什么启动了发布/订阅循环?是否在初始化期间将 flag 设置为 false? 2) 上面的无限循环示例内置了无限循环:发布者和订阅者作用于相同的属性。但在最初的问题中,订阅者修改了一个不相关的属性。从理论上讲,哪个不应该触发发布者发出另一个值,从而冻结反馈循环,是吗?
猜你喜欢
  • 1970-01-01
  • 2020-08-27
  • 2021-05-07
  • 2012-08-24
  • 2020-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多