SwiftUI中如何控制ViewModel里@Published属性更新的API调用次数?
批量更新ObservableObject的@Published属性时避免重复API调用
我有一个遵循ObservableObject协议的ViewModel类:
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。但当通过代码批量更新多个属性时(如下示例),会触发多次API调用和视图重绘:
viewModel.name = "test" viewModel.password = "newPassword" viewModel.oneAnotherProperty = "notUpdateAll"
如何在这种批量更新场景下让API只调用一次,同时保持单个属性更新时的正常行为?
方法1:手动控制变更通知与批量更新
给ViewModel添加一个批量更新方法,手动发送一次objectWillChange通知,一次性修改目标属性后统一调用API:
import Combine 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 cancellables = Set<AnyCancellable>() init() { // 单个属性更新时自动触发API setupPropertySubscriptions() } private func setupPropertySubscriptions() { let propertyPublishers = [ $name, $reversed, $password, $age, $address, $oneAnotherProperty ] Publishers.MergeMany(propertyPublishers) .sink { [weak self] _ in self?.callAPI() } .store(in: &cancellables) } // 批量更新专用方法 func batchUpdate(name: String? = nil, password: String? = nil, oneAnotherProperty: String? = nil) { // 手动发送变更通知,合并视图更新 objectWillChange.send() // 批量修改属性 if let name = name { self.name = name } if let password = password { self.password = password } if let oneAnotherProperty = oneAnotherProperty { self.oneAnotherProperty = oneAnotherProperty } // 重置订阅并手动调用一次API cancellables.removeAll() setupPropertySubscriptions() callAPI() } private func callAPI() { // 你的API调用逻辑 } }
使用时直接调用批量方法:
viewModel.batchUpdate(name: "test", password: "newPassword", oneAnotherProperty: "notUpdateAll")
方法2:用标志位跳过批量更新时的重复API调用
给ViewModel加一个临时标志,批量更新时开启标志阻止属性变更触发API,完成后手动调用一次API并关闭标志:
import Combine 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 isBatchUpdating = false private var cancellables = Set<AnyCancellable>() init() { setupPropertySubscriptions() } private func setupPropertySubscriptions() { let propertyPublishers = [ $name, $reversed, $password, $age, $address, $oneAnotherProperty ] Publishers.MergeMany(propertyPublishers) .sink { [weak self] _ in guard let self = self else { return } // 批量更新时跳过API调用 if !self.isBatchUpdating { self.callAPI() } } .store(in: &cancellables) } func batchUpdate(name: String, password: String, oneAnotherProperty: String) { isBatchUpdating = true // 批量修改属性 self.name = name self.password = password self.oneAnotherProperty = oneAnotherProperty isBatchUpdating = false // 批量完成后手动调用一次API callAPI() } private func callAPI() { // 你的API调用逻辑 } }
这种方法不修改原有订阅逻辑,单个属性更新时仍正常触发API,仅批量更新时合并调用。
方法3:利用withAnimation合并变更(极简方案)
如果API调用是通过视图onChange或objectWillChange订阅触发的,可将批量更新代码放在withAnimation闭包里(无需实际动画),SwiftUI会自动合并所有变更为一次通知:
withAnimation { viewModel.name = "test" viewModel.password = "newPassword" viewModel.oneAnotherProperty = "notUpdateAll" }
注意:若API是在属性didSet中调用的,此方法无效,因为didSet仍会在每次属性修改时执行。
内容的提问来源于stack exchange,提问作者arun siva
相关产品推荐
相关产品推荐

