我有一个具有多个@Published
属性的视图模型类。
class AddPassaround : ObservableObject {
@Published var name: String = ""
@Published var reversed : String = ""
@Published var password: String = ""
@Published var age: String = ""
@Published var address: String = ""
@Published var oneAnotherProperty: String = ""
init() {
}
}
当任何一个@Published
属性被更新时,我调用API。现在有另一个场景,需要更新多个@Published
属性一次编程。像这样
viewModel.name = "test"
viewModel.password = "newPassword"
viewModel.oneAnotherProperty = "notUpdateAll"
现在的问题是API被多次调用,视图被多次重载。在这种情况下,我如何使API只调用一次?在其他情况下应该能正常工作。
SwiftUI面临着同样的问题:当你更新你的ObservableObject
的三个单独的@Published
属性时,SwiftUI得到三次通知。
SwiftUI通过合并事件避免了三次更新UI。在收到第一个通知时,SwiftUI安排在run循环等待下一个事件之前被唤醒。在后面的通知中,SwiftUI看到它已经被安排唤醒,什么也不做。
UIKit, AppKit和Core Animation也合并显示更新;这就是UIView.setNeedsDisplay
和CALayer.setNeedsDisplay
方法的作用。
你可以使用相同类型的合并。一种方法是使用NotificationQueue
:
-
在
init
中,订阅私有Notification.Name
和发送方。当通知到达时,调用API。 -
同样在
init
中,订阅objectWillChange
输出。当objectWillChange
发送输出时,要求NotificationQueue.default
进入私有通知队列 -
将
.onName
和.onSender
合并标志传递给NotificationQueue
,以便它在事件循环的每轮只发送一次通知。
class AddPassaround : ObservableObject {
@Published var name: String = ""
@Published var reversed : String = ""
@Published var password: String = ""
@Published var age: String = ""
@Published var address: String = ""
@Published var oneAnotherProperty: String = ""
private var tickets: [AnyCancellable] = []
private let notificationSender = NSObject()
private var notificationName: Notification.Name { .init("AddPassaround call API") }
init() {
NotificationCenter.default.publisher(for: notificationName, object: notificationSender)
.sink { [weak self] _ in self?.callAPI() }
.store(in: &tickets)
objectWillChange
.sink { [weak self] _ in self?.scheduleCallAPI() }
.store(in: &tickets)
}
private func scheduleCallAPI() {
// Arrange to callAPI soon, if I haven't already arranged it.
NotificationQueue.default.enqueue(
.init(name: notificationName, object: notificationSender),
postingStyle: .whenIdle,
coalesceMask: [.onName, .onSender],
forModes: [.common]
)
}
private func callAPI() {
print("this is where you call the API")
}
}
我们使用一个单独的对象notificationSender
,而不是self
,以避免self
和NotificationCenter.default
之间的保留循环。
如果你只想让你的一些属性触发API调用,你可以给他们willSet
(或didSet
)观察者,而不是订阅objectWillChange
:
class AddPassaround : ObservableObject {
@Published var name: String = "" {
willSet { scheduleCallAPI() }
}
@Published var reversed : String = ""
@Published var password: String = "" {
willSet { scheduleCallAPI() }
}
@Published var age: String = ""
@Published var address: String = ""
@Published var oneAnotherProperty: String = ""
private var tickets: [AnyCancellable] = []
private let notificationSender = NSObject()
private var notificationName: Notification.Name { .init("AddPassaround call API") }
init() {
NotificationCenter.default.publisher(for: notificationName, object: notificationSender)
.sink { [weak self] _ in self?.callAPI() }
.store(in: &tickets)
}
private func scheduleCallAPI() {
// Arrange to callAPI soon, if I haven't already arranged it.
NotificationQueue.default.enqueue(
.init(name: notificationName, object: notificationSender),
postingStyle: .whenIdle,
coalesceMask: .onName,
forModes: [.common]
)
}
private func callAPI() {
print("this is where you call the API")
}
}