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 SnapKitclass IFLYKVODemosVC: IFLYCommonBaseVC {private let tableView = UITableView()private let offsetLabel: UILabel = {let label = UILabel()label.backgroundColor = UIColor.black.withAlphaComponent(0.6)label.textColor = .whitelabel.font = UIFont.systemFont(ofSize: 14)label.textAlignment = .centerlabel.layer.cornerRadius = 5label.clipsToBounds = truereturn 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 inmake.edges.equalToSuperview()}offsetLabel.snp.makeConstraints { make inmake.center.equalToSuperview()make.height.equalTo(30)make.width.equalTo(180)}contentOffsetObservation = tableView.observe(\.contentOffset, options: [.new]) { [weak self] tableView, change inguard let self = self, let newOffset = change.newValue else { return }let offsetY = newOffset.yself.offsetLabel.text = String(format: "Offset Y: %.2f", offsetY)self.offsetLabel.isHidden = falseNSObject.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 = selftableView.delegate = selftableView.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的原理和注意事項,可以更高效地實現數據變化的監聽與響應。