You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:27:04