在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*)dictionaryWithValuesForKeys:(NSArray *)keys;27 - (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;28 29 @end30 31 @interface NSArray (NSKeyValueCoding)32 33 - (id)valueForKey:(NSString *)key;34 - (void)setValue:(nullable id)value forKey:(NSString *)key;35 36 @end37 38 @interface NSDictionary (NSKeyValueCoding)39 40 - (nullable ObjectType)valueForKey:(NSString *)key;41 42 @end43 44 @interface NSMutableDictionary (NSKeyValueCoding)45 46 - (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;47 48 @end49 50 @interface NSOrderedSet (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 @end56 57 @interface NSSet (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 *)key4 {5 NSLog(@"不存在Key:%@", key);6 }
② 使用KVC取值的时候
如果当前类没有找到对应的Key值,系统会自动调用 valueForUndefinedKey: 方法
该方法的默认实现是抛出一个异常,如果不想抛出异常,就重写这个方法
1 // 重写2 // 使用KVC取值的时候3 - (id)valueForUndefinedKey:(NSString *)key4 {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*)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:字符串标识的key18 // 参数3:触发添加观察者对象的时候19 /*20 NSKeyValueObservingOptionNew = 0x01 key或value只要有一个更新的时候就会触发21 NSKeyValueObservingOptionOld = 0x0222 NSKeyValueObservingOptionInitial = 0x0423 NSKeyValueObservingOptionPrior = 0x0824 */25 // 参数4:文本内容 一般为nil26 [self addObserver:self forKeyPath:@"array" options:NSKeyValueObservingOptionNew context:nil];27 }
实现回调方法
1 // 第二步:实现回调 2 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)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*)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)animated3 {4 // 在不需要观察者的时候需要把它删除5 [self removeObserver:self forKeyPath:@"array"];6 }