-
(一) KVO定义
KVO :NSKeyValueObserving,键值观察。它是一种观察者模式的衍生。其基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,通过触发观察者对象实现的KVO接口方法,来自动的通知观察者。
-
(二) KVO三部曲
1)注册观察者
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionPrior context:@"context"];
参数一:observer:观察者,也就是KVO通知的订阅者。必须实现 observeValueForKeyPath
参数二:keyPath:描述将要观察的属性,相对于被观察者。
参数三:options:KVO的一些属性配置;有四个选项。
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld:change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前也要通知change字典包括(notificationIsPrior = 1 和旧值);
参数四:context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。
2)观察者通知
每当key发生变化时,都会触发这个调。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"keyPath:%@\nobject:%@\nchange:%@\ncontext:%@",keyPath,object,change,context);
}
3)注销观察者
- (void)dealloc
{
[self removeObserver:self.person forKeyPath:@"name"];
}
-
(三) KVO底层实现
自动键值观察是使用称为isa-swizzling的技术实现的。
该isa指针,顾名思义,指向对象的类,它保持一个调度表。该分派表实质上包含指向该类实现的方法的指针以及其他数据。
在为对象的属性注册观察者时,将修改观察对象的isa指针,指向中间类而不是真实类。结果,isa指针的值不一定反映实例的实际类。
您永远不要依靠isa指针来确定类成员。相反,您应该使用该class方法确定对象实例的类。
首先我们先来验证一下,是否存在中间类:
NSLog(@"监听之前类:%s - 内存地址:%p",object_getClassName(self.person),[self.person methodForSelector:@selector(setName:)]);
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"context"];
NSLog(@"监听之后类:%s - 内存地址:%p",object_getClassName(self.person),[self.person methodForSelector:@selector(setName:)]);
输出:
监听之前类:MMKVOPerson - 内存地址:0x103afc8e0
监听之后类:NSKVONotifying_MMKVOPerson - 内存地址:0x7fff25721c7a
由输出可以看出会存在一个
NSKVONotifying_+原类名的新类。并重写了setter方法。
1.我们在为一个对象添加观察者的时候,系统会为这个类派生出一个名为NSKVONotifying_+原类名 的类。
2.在这个类中会重写它的setter方法。并调用
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
来观察赋值前后的属性的值。然后触发回调通知。
-
(四) 禁止使用系统的KVO
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
注意:dealloc中的移除观察者要删掉,因为观察者没有注册上,会报because it is not registered as an observer.这个错误。
-
(五) 自定义KVO
//setName方法
void setterMethodIMP(id self, SEL _cmd, NSString *name){
//1.取出观察者
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"observer");
//2.取出keyPath
id keyPath = objc_getAssociatedObject(self, (__bridge const void *)@"keyPath");
//3.通知观察者
//[observer observeValueForKeyPath:(NSString *)keyPath ofObject:self change:@{(NSString *)keyPath:name} context:nil];
objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:),(NSString *)keyPath,self,@{(NSString *)keyPath:name},nil);
}
- (void)MM_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
NSLog(@"自定义KVO");
NSString *oldName = NSStringFromClass([self class]);
NSString *newName = [NSString stringWithFormat:@"MMKVONotifying_%@",oldName];
//创建一个名为MMKVONotifying_MMKVOPerson的类
Class newClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
//设置对象的isa指针 将self -> MMKVONotifying_MMKVOPerson
object_setClass(self, newClass);
//重写 setName 方法
//获取方法名称
NSString *methodName = [NSString stringWithFormat:@"set%@:",keyPath.capitalizedString];
SEL sel = NSSelectorFromString(methodName);
//添加一个自己的setter方法实现
class_addMethod(newClass, sel, (IMP)setterMethodIMP, "v@:@");
//添加关联着
objc_setAssociatedObject(self, (__bridge const void *)@"observer", observer, OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(self, (__bridge const void *)@"keyPath", keyPath, OBJC_ASSOCIATION_COPY);
}
- (void)dealloc
{
//释放观察者
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"observer");
observer = nil;
}
输出:
keyPath:name
object:<MMKVONotifying_MMKVOPerson: 0x60000106bbd0>
change:{
name = "\U9a6c\U5c0f\U6482*";
}
context:(null)
1.动态的创建一个MMKVONotifying_MMKVOPerson的类
2.将当前类的isa指向新类
3.将setter的SEL指向自己实现的IMP(setterMethodIMP),方法中调用objc_msgSend通知我们观察者
4.为observer和keyPath添加关联
5.重写dealloc方法,注销观察者