KVO(Key-Value Observing)與 KVC(Key-Value Coding)是 Objective-C 運行時的兩大動態特性支柱,分別實現了屬性變更通知與鍵路徑訪問。本文通過實戰場景剖析其底層機制與最佳實踐。
addObserver:forKeyPath:options:context:observeValueForKeyPath:ofObject:change:context:removeObserver:forKeyPath:(必須在 dealloc 前調用)- (void)viewDidLoad {
[super viewDidLoad];
// 觀察 scrollView.contentSize 變化
[_webView.scrollView addObserver:self
forKeyPath:@"contentSize"
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (object == _webView.scrollView && [keyPath isEqualToString:@"contentSize"]) {
CGSize size = _webView.scrollView.contentSize;
if (size.height > 568.0) { // 超過屏幕高度,說明有底部廣告
// 動態計算廣告位置並覆蓋
_hideBottomImage = [[UIImageView alloc] initWithFrame:
CGRectMake(0, size.height - 67, ScreenWidth, 67)];
_hideBottomImage.image = [UIImage imageNamed:@"banner"];
[_webView.scrollView addSubview:_hideBottomImage];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc {
[_webView.scrollView removeObserver:self forKeyPath:@"contentSize"];
[super dealloc];
}
NSKVONotifying_OriginalClass)isa 指針指向新子類willChangeValueForKey: → 原 setter → didChangeValueForKey:didChangeValueForKey: 內部遍歷觀察者列表觸發回調注意:直接修改實例變量(_ivar = newValue)不會觸發 KVO,必須通過 setValue:forKey: 或合規 setter。
// 模型定義
@interface HMTStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) HMTClass *hmtClass;
@end
// KVC 賦值與取值
HMTStudent *student = [[HMTStudent alloc] init];
[student setValue:@"張三" forKey:@"name"]; // 等同 student.name = @"張三"
[student setValue:@(18) forKey:@"age"]; // 自動箱裝基本類型
NSString *name = [student valueForKey:@"name"]; // 等同 student.name
// 設置關聯對象屬性
HMTClass *cls = [[HMTClass alloc] init];
[cls setValue:@"一年級一班" forKey:@"name"];
[student setValue:cls forKey:@"hmtClass"];
// 穿透訪問:student.hmtClass.name
NSString *className = [student valueForKeyPath:@"hmtClass.name"];
// 穿透賦值
[student setValue:@"二年級一班" forKeyPath:@"hmtClass.name"];
| 特性 | 說明 | 示例 |
|---|---|---|
| 自動箱裝/拆箱 | 基本類型與 NSNumber/NSValue 互轉 | [student setValue:@"100" forKey:@"age"] → NSInteger |
| 集合操作符 | @avg、@sum、@max、@min、@count、@distinctUnionOfObjects | [students valueForKeyPath:@"@avg.age"] |
| 集合訪問 | 有序/無序集合的索引/鍵訪問 | [student mutableArrayValueForKey:@"friends"] |
| Boolean 屬性解析規則 | 優先序:_isKey > _key > isKey > key | 見下文驗證代碼 |
@interface TestBoolean : NSObject
{
BOOL _test; // 1. 下劃線+is+首字母大寫
BOOL _isTest; // 2. 下劃線+is+首字母大寫(重複)
BOOL test; // 3. 直接屬性名
BOOL isTest; // 4. is+首字母大寫
}
@property (nonatomic, assign) BOOL test;
@property (nonatomic, assign) BOOL isTest;
@end
// 實測設置順序:_test → _isTest → test → isTest
// 即:_is<Key> > _<Key> > is<Key> > <Key>
dealloc 或 viewDidDisappear 移除context 指針避免父類觀察者衝突[obj willChangeValueForKey:@"key"]; ... [obj didChangeValueForKey:@"key"]; 手動控制通知粒度validate<Key>:error: 實現屬性級驗證邏輯KVO 基於 Runtime 的 isa-swizzling 實現非侵入式屬性監聽,KVC 提供了鍵路徑的動態對象圖遍歷能力。兩者結合構成了 Cocoa 綁定、Core Data、ReactiveCocoa 等上層框架的基石。掌握其觸發條件、生命週期陷阱、性能特徵,是進階 iOS/macOS 開發的必修課。