我试图使用SwiftUI和Combine创建一个动态窗体,该窗体基于另一个输入(在示例number
中)加载一个输入(在示例中,myString
)的选项。 / p>
问题在于即使值从未更改,Combo堆栈也将连续执行,发出大量网络请求(在此示例中,通过延迟进行模拟)。
我认为预期的行为是$myString
仅在其更改时才发布值。
class MyModel: ObservableObject {
// My first choice on the form
@Published var myString: String = "Jhon"
// My choice that depends on myString
@Published var number: Int?
var updatedImagesPublisher: AnyPublisher<Int, Never> {
return $myString
.removeDuplicates()
.print()
.flatMap { newImageType in
return Future<Int, Never> { promise in
print("Executing...")
// Simulate network request
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let newNumber = Int.random(in: 1...200)
return promise(.success(newNumber))
}
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
struct ContentView: View {
@ObservedObject var model: MyModel = MyModel()
var body: some View {
Text("\(model.number ?? -100)")
.onReceive(model.updatedImagesPublisher) { newNumber in
self.model.number = newNumber
}
}
}
答案 0 :(得分:3)
问题是updatedImagesPublisher
是一个计算属性。这意味着您每次访问都会创建一个新实例。代码中会发生什么。 Text
对象订阅updatedImagesPublisher
,当它收到一个新值时,它将更新Model的number
属性。 number
是@Published
的属性,这意味着objectWillChange
方法将在您每次更改它时被调用,并且将重新创建主体。新的Text
将订阅新的updatedImagesPublisher
(因为它是计算属性)并再次接收该值。为了避免这种行为,只需使用惰性属性而不是计算属性。
lazy var updatedImagesPublisher: AnyPublisher<Int, Never> = {
return $myString
.removeDuplicates()
.print()
.flatMap { newImageType in
return Future<Int, Never> { promise in
print("Executing...")
// Simulate network request
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let newNumber = Int.random(in: 1...200)
return promise(.success(newNumber))
}
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}()
答案 1 :(得分:1)
我认为这是因为您为每个视图更新创建了新的发布者,请尝试以下操作。 (已通过Xcode 11.4测试)
class MyModel: ObservableObject {
// My first choice on the form
@Published var myString: String = "Jhon"
// My choice that depends on myString
@Published var number: Int?
lazy var updatedImagesPublisher: AnyPublisher<Int, Never> = {
return $myString
.removeDuplicates()
.print()
.flatMap { newImageType in
return Future<Int, Never> { promise in
print("Executing...")
// Simulate network request
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let newNumber = Int.random(in: 1...200)
return promise(.success(newNumber))
}
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}()
}