Swift 4中KVO监听器问题:如何监听ViewModel的loginResponse变量变化?
嗨,我来帮你搞定给ViewModel里的loginResponse添加观察者监听变化的事儿~首先我先把你没写完的ViewModel代码补全了,方便后续演示:
@objcMembers class ViewModel: NSObject { var count = 300 @objc dynamic var loginResponse: String override init() { loginResponse = "1" super.init() setupTimer() } func setupTimer(){ _ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector:#selector(callTimer), userInfo: nil, repeats: true) } @objc func callTimer(){ count -= 1 // 补上倒计时减1的逻辑 let minutes = String(count / 60) let seconds = String(count % 60) loginResponse = String(format: "%@:%@", minutes, seconds) // 格式化倒计时赋值给loginResponse } }
下面给你两种iOS开发里常用的监听方案,按需选择:
方案一:使用KVO(Key-Value Observing)
因为你已经给loginResponse标记了@objc dynamic,完全符合KVO的要求,直接在需要监听的页面(比如ViewController)里添加观察者就行:
1. 添加观察者逻辑
class LoginViewController: UIViewController { private let viewModel = ViewModel() // 保存观察者对象,方便后续移除 private var loginResponseObservation: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() // 开始监听loginResponse的变化 loginResponseObservation = viewModel.observe(\.loginResponse, options: [.old, .new]) { [weak self] (_, change) in guard let self = self else { return } print("loginResponse更新啦:旧值\(change.oldValue ?? ""),新值\(change.newValue ?? "")") // 这里直接更新UI就行,比如把倒计时显示到label上 // self.countdownLabel.text = change.newValue } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 页面销毁前移除观察者,避免内存泄漏 loginResponseObservation?.invalidate() } }
注意事项
- 一定要用
[weak self]避免循环引用,毕竟ViewModel里的Timer已经持有了self,再加观察者的话容易出内存问题 - 必须保存
NSKeyValueObservation对象,不然观察者会被自动释放,监听失效
方案二:使用Combine(Swift 5.1+)
如果你的项目支持iOS 13+,Combine框架是更现代、类型安全的选择,不需要依赖Objective-C的运行时特性:
1. 修改ViewModel适配Combine
import Combine // 如果不需要NSObject的特性,也可以改成普通Swift类 class ViewModel: NSObject { var count = 300 // 用@Published标记,自动发送值变化通知 @Published var loginResponse: String = "1" override init() { super.init() setupTimer() } func setupTimer(){ // 用Combine风格的Timer,避免循环引用 Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in guard let self = self else { return } self.count -= 1 let minutes = String(self.count / 60) let seconds = String(self.count % 60) self.loginResponse = String(format: "%@:%@", minutes, seconds) } .store(in: &cancellables) } // 管理所有Combine订阅,页面销毁时自动取消 private var cancellables = Set<AnyCancellable>() }
2. 在ViewController里订阅变化
import Combine class LoginViewController: UIViewController { private let viewModel = ViewModel() private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() // 订阅loginResponse的变化,自动在主线程更新UI viewModel.$loginResponse .receive(on: DispatchQueue.main) .sink { [weak self] newResponse in print("loginResponse新值:\(newResponse)") // self?.countdownLabel.text = newResponse } .store(in: &cancellables) } }
方案优势
- 类型安全,不会出现KVO里字符串写错属性名的问题
- 自动管理订阅,当ViewController销毁时,
cancellables集合会自动取消所有订阅,不用手动移除 - 可以结合Combine的各种操作符(比如过滤、映射)做更复杂的逻辑处理
内容的提问来源于stack exchange,提问作者Chandan kumar
相关产品推荐
相关产品推荐

