在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 }