【原】iOS中KVC和KVO的區別


在iOS開發中經常會看到KVC和KVO這兩個概念,比較可能混淆,特地區分一下

KVC(Key Value Coding)

 1> 概述

  KVC:Key Value Coding,鍵值編碼,是一種間接訪問實例變量的方法。

  KVC 提供了一個使用字符串(Key)而不是訪問器方法,去訪問一個對象實例變量的機制。

 2> KVC部分源碼(頭文件)

 1 // NSKeyValueCoding.h
 2 @interface NSObject(NSKeyValueCoding)
 3 
 4 + (BOOL)accessInstanceVariablesDirectly;
 5 
 6 - (nullable id)valueForKey:(NSString *)key;
 7 - (void)setValue:(nullable id)value forKey:(NSString *)key;
 8 - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
 9 
10 - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
11 
12 - (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);
13 
14 - (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
15 
16 - (nullable id)valueForKeyPath:(NSString *)keyPath;
17 - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
18 - (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
19 - (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
20 - (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath NS_AVAILABLE(10_7, 5_0);
21 - (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
22 
23 - (nullable id)valueForUndefinedKey:(NSString *)key;
24 - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
25 - (void)setNilValueForKey:(NSString *)key;
26 - (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
27 - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
28 
29 @end
30 
31 @interface NSArray<ObjectType>(NSKeyValueCoding)
32 
33 - (id)valueForKey:(NSString *)key;
34 - (void)setValue:(nullable id)value forKey:(NSString *)key;
35 
36 @end
37 
38 @interface NSDictionary<KeyType, ObjectType>(NSKeyValueCoding)
39 
40 - (nullable ObjectType)valueForKey:(NSString *)key;
41 
42 @end
43 
44 @interface NSMutableDictionary<KeyType, ObjectType>(NSKeyValueCoding)
45 
46 - (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
47 
48 @end
49 
50 @interface NSOrderedSet<ObjectType>(NSKeyValueCoding)
51 
52 - (id)valueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);
53 - (void)setValue:(nullable id)value forKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);
54 
55 @end
56 
57 @interface NSSet<ObjectType>(NSKeyValueCoding)
58 
59 - (id)valueForKey:(NSString *)key;
60 - (void)setValue:(nullable id)value forKey:(NSString *)key;
61 
62 @end

  可以看到這個類里面包含了對類NSObject、NSArray、NSDictionary、NSMutableDictionary、NSOrderedSet、NSSet的擴展,擴展方法基本上為

- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;

也就是說,基本上Objective-C里所有的對象都支持KVC操作,操作包含如上兩類方法,動態讀取和動態設值。

 3> 通過KVC鍵值編碼訪問屬性

  ① key值查找

1 [stu setValue:@"xiaoqiang" forKey:@"name"]; 
2 [stu setValue:@"boy" forKey:@"gender"];
3 [stu setValue:@24 forKey:@"age"]; 
4 
5 NSLog(@"name = %@, gender = %@, age = %@", [stu valueForKey:@"name"], [stu valueForKey:@"gender"], [stu valueForKey:@"age"]);

  ② 路徑查找

1 Teacher *tea = [[Teacher alloc] init];
2 stu.teacher = tea;
3 [stu setValue:@"fangfang" forKeyPath:@"teacher.name"];
4 
5 // 路徑查找
6 NSLog(@"teacherName = %@", [stu valueForKeyPath:@"teacher.name"]);

  ③ 同時給多個屬性賦值

 1 NSDictionary *dict = @{
 2                        @"name" : @"fangfang",
 3                        @"gender" : @"girl",
 4                        @"age" : @18,
 5                        @"hobby" : @"fangfang"
 6                        };
 7 Student *stu2 = [[Student alloc] init];
 8 
 9 // 同時給多個屬性賦值
10 [stu2 setValuesForKeysWithDictionary:dict];
11 
12 NSLog(@"name = %@, gender = %@, age = %ld", stu2.name, stu2.gender, stu2.age);

 4> KVC拋出異常的方法

  ① 使用KVC設置值對象時

   如果當前類沒有找到對象的Key值,系統會自動調用 setValue: forUndefinedKey: 方法

   該方法的默認實現是拋出一個異常,如果不想拋出異常,就重寫這個方法

1 // 重寫
2 // 使用KVC設置值對象
3 - (void)setValue:(id)value forUndefinedKey:(NSString *)key
4 {
5     NSLog(@"不存在Key:%@", key);
6 }

  ② 使用KVC取值的時候

     如果當前類沒有找到對應的Key值,系統會自動調用 valueForUndefinedKey: 方法

     該方法的默認實現是拋出一個異常,如果不想拋出異常,就重寫這個方法

1 // 重寫
2 // 使用KVC取值的時候
3 - (id)valueForUndefinedKey:(NSString *)key
4 {
5     return nil;
6 }

 5> KVC的實現機制 

  KVC按順序使用如下技術:

  • 檢查是否存在getter方法-<key>或者setter方法-set<key>:的方法;        
  • 如果沒有上述方法,則檢查是否存在名字為-_<key><key>的實例變量;        
  • 如果仍未找到,則調用 valueForUndefinedKey:setValue: forUndefinedKey: 方法。這些方法的默認實現都是拋出異常,我們可以根據需要重寫它們。

KVO(Key Value Observer)

 1> 概述

  KVO:(Key Value Observer)鍵值觀察者,是觀察者設計模式的一種具體實現

  KVO觸發機制:一個對象(觀察者),監測另一對象(被觀察者)的某屬性是否發生變化,若被監測的屬性發生的更改,會觸發觀察者的一個方法(方法名固定,類似代理方法)

 2> 一部分NSKeyValueObserving.h對於NSObject的拓展代碼

 1 @interface NSObject(NSKeyValueObserving)
 2 
 3 - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
 4 
 5 @end
 6 
 7 @interface NSObject(NSKeyValueObserverRegistration)
 8 
 9 - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
10 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
11 - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
12 
13 @end

  從拓展名稱就可以看出,使用KVO需要注冊監聽器,也需要刪除監聽器。監聽過程需要使用observeValueForKeyPath回調方法。

 3> 使用步驟

  注冊觀察者(為被觀察者指定觀察者以及被觀察屬性)

 1 // KVO 鍵值觀察,是觀察者設計模式
 2 @interface ViewController ()
 3 
 4 // 觀察可變數組的改變情況(蘋果官方文檔不建議對數組進行觀察)
 5 @property (nonatomic, strong) NSMutableArray *array;
 6 
 7 @end
 8 
 9 - (void)viewDidLoad {
10     [super viewDidLoad];
11     // Do any additional setup after loading the view, typically from a nib.
12     
13     self.array = [NSMutableArray array];
14     
15     // 第一步:注冊觀察者
16     // 參數1:添加的觀察者對象
17     // 參數2:字符串標識的key
18     // 參數3:觸發添加觀察者對象的時候
19     /*
20      NSKeyValueObservingOptionNew = 0x01 key或value只要有一個更新的時候就會觸發
21      NSKeyValueObservingOptionOld = 0x02
22      NSKeyValueObservingOptionInitial = 0x04
23      NSKeyValueObservingOptionPrior = 0x08
24      */
25     // 參數4:文本內容 一般為nil
26     [self addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew context:nil];
27 }

  
實現回調方法

 1 // 第二步:實現回調
 2 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
 3 {
 4     
 5     NSLog(@"keyPath = %@", keyPath);
 6     NSLog(@"object = %@", object);
 7     NSLog(@"change = %@", change);
 8     
 9     // 可以進行刷新UI的操作
10 }

  觸發回調方法(被觀察屬性發生更改)

 1 // 第二步:實現回調
 2 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
 3 {
 4     
 5     NSLog(@"keyPath = %@", keyPath);
 6     NSLog(@"object = %@", object);
 7     NSLog(@"change = %@", change);
 8     
 9     // 可以進行刷新UI的操作
10 }

  移除觀察者

   在不需要觀察者的時候需要把它刪除,本人就只在視圖將要消失時移除

1 // 視圖將要消失的時候
2 - (void)viewWillDisappear:(BOOL)animated
3 {
4     // 在不需要觀察者的時候需要把它刪除
5     [self removeObserver:self forKeyPath:@"array"];
6 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM