首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Objective-C KVO 與 KVC 機制深度解析:從原理到實戰

     編輯:Java 相關知識

前言

KVO(Key-Value Observing)與 KVC(Key-Value Coding)是 Objective-C 運行時的兩大動態特性支柱,分別實現了屬性變更通知與鍵路徑訪問。本文通過實戰場景剖析其底層機制與最佳實踐。

一、KVO:屬性觀察的「發布-訂閱」模式

1.1 核心流程四步走

  1. 註冊:addObserver:forKeyPath:options:context:
  2. 觸發:通過 KVC 合規的 setter 修改屬性
  3. 回調:observeValueForKeyPath:ofObject:change:context:
  4. 移除:removeObserver:forKeyPath:(必須在 dealloc 前調用)

1.2 實戰場景:WebView 廣告屏蔽

- (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];
}

1.3 底層原理:isa-swizzling 動態子類化

  • 註冊觀察者時,Runtime 動態創建原類的子類(如 NSKVONotifying_OriginalClass)
  • 修改對象的 isa 指針指向新子類
  • 子類重寫 setter 方法:willChangeValueForKey: → 原 setter → didChangeValueForKey:
  • didChangeValueForKey: 內部遍歷觀察者列表觸發回調

注意:直接修改實例變量(_ivar = newValue)不會觸發 KVO,必須通過 setValue:forKey: 或合規 setter。

二、KVC:鍵值編碼的動態訪問協議

2.1 基礎訪問:setValue:forKey: / valueForKey:

// 模型定義
@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

2.2 鍵路徑訪問:穿透對象圖


// 設置關聯對象屬性
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"];

2.3 進階特性

特性說明示例
自動箱裝/拆箱基本類型與 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見下文驗證代碼

2.4 Boolean 解析優先級驗證

@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>

三、KVO/KVC 協同最佳實踐

  1. 觀察者生命週期管理:嚴格配對 add/remove,建議在 dealloc 或 viewDidDisappear 移除
  2. 上下文指針區分觀察源:使用唯一 context 指針避免父類觀察者衝突
  3. 批量更新優化:[obj willChangeValueForKey:@"key"]; ... [obj didChangeValueForKey:@"key"]; 手動控制通知粒度
  4. KVC 驗證攔截:實現 validate<Key>:error: 實現屬性級驗證邏輯
  5. 性能考量:高頻屬性變更避免 KVO,改用 Delegate/Block/Combine 框架

四、總結

KVO 基於 Runtime 的 isa-swizzling 實現非侵入式屬性監聽,KVC 提供了鍵路徑的動態對象圖遍歷能力。兩者結合構成了 Cocoa 綁定、Core Data、ReactiveCocoa 等上層框架的基石。掌握其觸發條件、生命週期陷阱、性能特徵,是進階 iOS/macOS 開發的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved