KVO的使用
KVO使用起来非常简单,三个步骤就搞定啦
1、通过
addObserver: forKeyPath: options: context
方法注册成为观察者,这样就可以观察到keyPath属性变化事件
2、实现observeValueForKeyPath: ofObject: change:context:
方法,当属性值发生变化,KVO会回调这个方法通知观察者
3、当不需要监听的时候调用removeObserver: forKeyPath
将KVO移除
KVO的触发模式
KVO有两种触发模式,手动和自动(不设置默认为自动)
来看看手动怎么触发
//在观察者的类中实现下面这个方法
// 1、模式调整 返回YES为自动 NO为手动
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
if ([key isEqualToString:@"name"]) {
return NO;
}
return YES;
}
//2、手动触发KVO
[_p willChangeValueForKey:@"name"];
_p.name = [NSString stringWithFormat:@"%d",a++];
[_p didChangeValueForKey:@"name"];
手动触发的好处就是我们可以根据需求的不同来决定要不要触发KVO
KVO观察对象属性
假设我们有一个Dog类,类里面有age、level两个属性,那么我们怎么使用KVO观察对象呢?
//在观察者的类中实现下面这个类方法
+(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet* keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"dog"]) {
keyPaths = [[NSSet alloc]initWithObjects:@"_dog.age",@"_dog.level", nil];
}
return keyPaths;
}
//注册观察者 观察dog属性
[_p addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];
简直是 so easy 有木有啊!
不过,作为一名程序员,就要有打破砂锅问到底的精神,KVO究竟是怎么实现的呢?
下面一起来揭开KVO的神秘面纱,let's go
KVO原理探究
KVO的内部实现分以下三个步骤
1、创建一个子类(为什么KVO的实现使用继承而不使用分类?因为KVO会重写
set
方法,而使用分类重写set
方法会覆盖掉原来类的set
方法)
2、重写set
方法
3、外界改变isa指针
-(void)JYC_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{
//1、创建一个类 父类为self即调用者
NSString* oldClassName = NSStringFromClass([self class]);
NSString* newClassName = [@"JYCkvo_" stringByAppendingString:oldClassName];
Class myClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
objc_registerClassPair(myClass);//注册类
//2. 重写set方法(实际上添加了set方法,子类中没有没有父类的方法,仅仅是可以调用) myclass
class_addMethod(myClass, @selector(setName:), (IMP)setName, "V@:@");
//3、修改isa指针(将调用者指向子类),这样调用者调用set方法会来到本类中重写的set方法中
object_setClass(self, myClass);
//4、将观察者保存到当前对象 OBJC_ASSOCIATION_ASSIGN 属性类型是weak,防止循环引用
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
}
void setName(id self, SEL _cmd ,NSString* newname){
NSLog(@"---%@",newname);
// 调用父类的setName:方法
Class class = [self class];
object_setClass(self, class_getSuperclass(class));//当前类改为父类
objc_msgSend(self, @selector(setName:),newname);
// 拿到观察者
id observer = objc_getAssociatedObject(self, "observer");
if(observer){
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),@"name",self,@{@"new:":newname,@"kind:":@1},nil);
}
//改回子类
object_setClass(self, class);
}
网友评论