KVO概述
KVO(Key-Value-Observing)是iOS开发中一种观察者模式实现,允许对象监听另一个对象属性的变化。当被观察属性的值发生变化时,观察者会收到通知。KVO基于NSKeyValueObserving协议实现,是Foundation框架的核心功能之一。
1.KVO的基本使用
我们以下面的demo为例,当我们滑动列表的时候,页面上显示UITableView的偏移量。
图1.KVO监听UITableView滑动时候的偏移量
具体的使用步骤如下:
1.添加观察者
通过addObserver:forKeyPath:options:context:
方法注册观察者。
observedObject.addObserver(
observer,
forKeyPath: "propertyName",
options: [.new, .old],
context: nil
)
options
:指定接收变化前的值(.old
)或变化后的值(.new
)。
context
:区分不同观察事件的上下文指针(通常用nil
)。
2.实现回调方法
观察者需重写observeValue(forKeyPath:of:change:context:)
方法处理通知:
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
if keyPath == "propertyName" {
let oldValue = change?[.oldKey]
let newValue = change?[.newKey]
// 处理值变化
}
}
3.移除观察者
在观察者销毁前必须调用removeObserver:forKeyPath:
observedObject.removeObserver(observer, forKeyPath: "propertyName")
4.完整的代码
完整的代码如下:
import UIKit
import IFLYCommonKit
import SnapKit
class IFLYKVODemosVC: IFLYCommonBaseVC {
private let tableView = UITableView()
private let offsetLabel: UILabel = {
let label = UILabel()
label.backgroundColor = UIColor.black.withAlphaComponent(0.6)
label.textColor = .white
label.font = UIFont.systemFont(ofSize: 14)
label.textAlignment = .center
label.layer.cornerRadius = 5
label.clipsToBounds = true
return label
}()
private var contentOffsetObservation: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
title = "KVO原理"
// Do any additional setup after loading the view.
view.addSubview(self.tableView)
view.addSubview(offsetLabel)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
offsetLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
make.height.equalTo(30)
make.width.equalTo(180)
}
contentOffsetObservation = tableView.observe(\.contentOffset, options: [.new]) { [weak self] tableView, change in
guard let self = self, let newOffset = change.newValue else { return }
let offsetY = newOffset.y
self.offsetLabel.text = String(format: "Offset Y: %.2f", offsetY)
self.offsetLabel.isHidden = false
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.hideOffsetLabel), object: nil)
self.perform(#selector(self.hideOffsetLabel), with: nil, afterDelay: 0.5)
}
}
@objc private func hideOffsetLabel() {
offsetLabel.isHidden = true
}
deinit {
contentOffsetObservation?.invalidate()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
}
extension IFLYKVODemosVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Row \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print("Selected row \(indexPath.row)")
}
}
2.KVO的实现原理
1.动态子类(Dynamic Subclassing)
KVO会在运行时动态生成被观察类的子类(命名规则为NSKVONotifying_ClassName
),并重写被观察属性的setter方法。
2.Setter方法重写
动态子类的setter方法会调用以下流程:
willChangeValueForKey:
:通知系统属性即将变化。- 调用原始setter方法修改属性值。
didChangeValueForKey:
:通知系统属性已变化,触发观察者回调。
3.手动触发KVO
即使未直接赋值,手动调用willChangeValueForKey:
和didChangeValueForKey:
也能触发通知:
observedObject.willChangeValue(forKey: "propertyName")
// 手动修改属性值(如直接操作实例变量)
observedObject.didChangeValue(forKey: "propertyName")
4.isa-swizzling
动态子类会修改被观察对象的isa
指针,指向新生成的子类,从而让方法调用路由到重写的逻辑。
3.KVO的注意事项
1.移除观察者
未移除观察者会导致 crash。观察者销毁前需确保移除所有观察。
2.线程安全
KVO通知与属性修改在同一线程触发,跨线程观察需谨慎处理线程同步。
3.性能优化
避免高频属性(如滚动时的UI坐标)使用KVO,频繁通知可能影响性能。
4.Swift中的使用
在Swift中需将观察对象和属性标记为@objc dynamic
:
@objc class MyClass: NSObject {
@objc dynamic var value: Int = 0
}
4.KVO的替代方案
- Combine框架:适用于Swift项目,提供更现代的响应式编程支持。
- Delegate模式:适合一对一的属性监听场景。
- Property Observers(Swift):直接使用
didSet
和willSet
监听属性变化。
通过理解KVO的原理和注意事项,可以更高效地实现数据变化的监听与响应。