在 Swift 中,KVO(Key-Value Observing)是一种观察属性变化的机制,通常用于监听 Objective-C 对象的属性变化。由于 Swift 中的原生类并不直接支持 KVO,因此需要使用 @objc 和 dynamic 修饰符来启用 KVO 功能。
以下是使用 KVO 实现属性监听的步骤和示例:
1. 实现 KVO 的基本步骤
-
标记属性为
@objc和dynamic:@objc:使属性对 Objective-C 运行时可见。dynamic:告诉编译器使用动态派发,而不是静态派发,以便支持 KVO。
-
添加观察者:
- 使用
addObserver(_:forKeyPath:options:context:)方法添加观察者。
- 使用
-
实现观察者回调:
- 实现
observeValue(forKeyPath:of:change:context:)方法,处理属性变化。
- 实现
-
移除观察者:
- 在不需要监听时,使用
removeObserver(_:forKeyPath:)移除观察者,避免内存泄漏。
- 在不需要监听时,使用
2. 示例代码
以下是一个简单的示例,演示如何使用 KVO 监听一个属性的变化:
import Foundation
// 1. 定义一个类,并标记需要监听的属性为 @objc 和 dynamic @objc class User: NSObject { @objc dynamic var name: String
init(name: String) {
self.name = name
}
}
// 2. 定义观察者 class Observer: NSObject { var user: User
init(user: User) {
self.user = user
super.init()
// 3. 添加观察者
user.addObserver(self, forKeyPath: "name", options: [.new, .old], context: nil)
}
deinit {
// 4. 移除观察者
user.removeObserver(self, forKeyPath: "name")
}
// 5. 实现观察者回调
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "name" {
let oldName = change?[.oldKey] as? String ?? "未知"
let newName = change?[.newKey] as? String ?? "未知"
print("属性 name 发生变化: 旧值 = \(oldName), 新值 = \(newName)")
}
}
}
// 测试 let user = User(name: "Alice") let observer = Observer(user: user)
// 修改属性,触发 KVO user.name = "Bob" user.name = "Charlie"
3. 代码说明
-
@objc dynamic var name: String:- 将
name属性标记为@objc和dynamic,使其支持 KVO。
- 将
-
addObserver:- 在
Observer类的初始化方法中,为user对象的name属性添加观察者。
- 在
-
observeValue(forKeyPath:of:change:context:):- 当
name属性发生变化时,会触发此方法,打印旧值和新值。
- 当
-
removeObserver:- 在
Observer类的deinit方法中移除观察者,避免内存泄漏。
- 在
4. 注意事项
-
KVO 的局限性:
- KVO 是基于 Objective-C 运行时的特性,因此只能用于继承自
NSObject的类。 - 在 Swift 中,KVO 的使用不如 SwiftUI 中的
@State和@Binding方便,建议在纯 Swift 项目中优先使用 SwiftUI 或 Combine 框架。
- KVO 是基于 Objective-C 运行时的特性,因此只能用于继承自
-
线程安全:
- KVO 的观察者回调可能在任意线程触发,因此需要注意线程安全问题。
-
性能开销:
- KVO 会引入一定的性能开销,尤其是在监听大量属性时。