什么是KVO
- KVO是Key-Value Observing的首字母缩写
- KVO是Object-C对观察者设计模式的实现
- Apple使用了isa混写(isa-swizzling)来实现KVO
KVO 提供一种机制,指定一个被观察对象(例如 A 类),当对象某个属性(例如 A 中的字符串 name)发生更改时,对象会获得通知,并作出相应处理;【且不需要给被观察的对象添加任何额外代码,就能使用 KVO 机制】
用一张图来描述一下KVO的实现机制
上图可以看出,注册一个对象的观察者的时候,实际上是调用了系统的
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
这个方法,调用这个方法后观察者观察对象A中的某个属性,然后系统会在运行时动态的创建一个NSKVONotifying_A的这么样一个类,原来的对象A的isa指针重新指向了NSKVONotifying_A这个类,把isa的指向进行修改就是isa混写技术.NSKVONotifying_A是类A的子类,并重写了其中的Setter方法,通过对Setter方法的重写达到可以通知所有观察者的目的.接下来,在XCode工程当中,来实际通过Setter方法的设置,KVO的监听来感受一下KVO的实现.
创建两个文件,MyObject和MyObserver.
- MyObject
@interface MyObject : NSObject
@property (nonatomic,assign) int value;
-(void)increase;
@end
@implementation MyObject
-(instancetype)init
{
self = [super init];
if (self) {
_value = 0;
}
return self;
}
-(void)increase
{
_value += 1;
}
@end
- MyObserver
@implementation MyObserver
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSNumber *valueNum = [change valueForKey:NSKeyValueChangeNewKey];
NSLog(@"value is %@",valueNum);
}
@end
然后在AppDelegate中进行KVO的监听
- AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
MyObject *obj = [[MyObject alloc]init];
MyObserver *observer = [[MyObserver alloc]init];
//调用KVO方法监听obj的value属性的变化
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
obj.value = 1;
return YES;
}
可以看到控制台打印出了结果
说明监听成功了.
在obj.value那里打个断点,看看MyObject是怎么被改写的.
不出所料,在监听的属性Value被改写后,MyObject变成了NSKVONotifying_MyObject了.
为什么调用Setter方法就可以实现这种KVO的监听呢.
重写的Setter添加的方法
- -(void)willChangeValueForKey:(NSString *)key
- -(void)didChangeValueForKey:(NSString *)key
那么在NSKVONotifying_MyObject中的Setter方法就变成了下面这样
-(void)setValue:(id)obj
{
[self willChangeValueForKey:@"keyPath"];
//调用父类,也就是原类的实现
[super setValue:obj];
[self didChangeValueForKey:@"keyPath"];
}
接下来有两个问题.
1.通过KVC设置Value能否生效
这个问题用代码来验证一下就可以了,如下所示
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MyObject *obj = [[MyObject alloc]init];
MyObserver *observer = [[MyObserver alloc]init];
//调用KVO方法监听obj的value属性的变化
[obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
obj.value = 1;
// 使用kvc来改变value的值
[obj setValue:@2 forKey:@"value"];
return YES;
}
结果控制台打印如下:
说明使用KVC设置属性的方式是可以出发KVO的,说明KVC设置属性是触发了Setter方法
2.使用成员变量赋值会出发KVO吗
我们在AppDelegate调用obj的increase方法,发现控制台只打印了value is 1,说明对成员变量赋值不会触发KVO,但对increate方法进行以下操作就不一样了.
不过如果我们把increase方法变成下面这样,再运行试试
-(void)increase
{
[self willChangeValueForKey:@"value"];
_value += 1;
[self didChangeValueForKey:@"value"];
}
发现又触发了KVO
所以根据上面的实验总结出下面几点
- 使用setter方法改变值KVO才会生效
- 使用setValue:forKey:改变KVO才会生效
- 成员变量直接修改需手动添加KVO才会生效
KVC
KVC是Key-Value coding的缩写,也就是键值编码,和键值编码相关的两个方法就是下面这两个
- -(id)valueForKey:(NSString *)key
这个可以调用某个实例的ValueForKey:方法,来获取和key同名或相似名称的实例变量的值 - -(void)setValue forKey:(NSString *)key
根据这个方法可以设置某一个对象和这个key同名或者相似名称的实例变量的值.