KVO
概述
KVO的全称是NSKeyValueObserving,对象采用的一种非正式协议,当其他对象的指定属性发生变化时,通知对象。由于KVO的机制,只对对象的属性起作用,一般继承自NSObject都支持KVO。
您可以观察任何对象属性,包括简单属性、一对一关系和多对多关系。
使用
KVO使用通常需要三个步骤
- 通过
addObserver:forKeyPath:options:context:
注册观察者,来观察keyPath的变化 - 当相对于被观察对象的指定键路径的值发生变化时,通知观察对象的
observeValueForKeyPath:ofObject:change:context:
这个方法,观察者应当实现该方法 - 当不再需要观察对象属性变化时,通过调用
removeObserver:forKeyPath:
或者removeObserver:forKeyPath:context:
阻止观察者对象接收与接收此消息的对象相关的键路径指定的属性的更改通知.
注:addObserver
与removeObserver
应当一一对应
注册方法
在注册时,options
的值为NSKeyValueObservingOptions
枚举类型
。当为NSKeyValueObservingOptionNew
、NSKeyValueObservingOptionOld
时,表示接收新值和旧值,默认为只接收新值。如果想在注册观察者后,立即接收一次回调,则可以加入NSKeyValueObservingOptionInitial
枚举
实现原理
KVO
是通过isa-swizzling
技术实现的。在运行时根据原类创建一个中间类,这个中间类是原类的子类,并动态修改当前对象的isa
指向中间类。并且将class
方法重写,返回原类的Class
。所以苹果建议在开发中不应该依赖isa
指针,而是通过class
实例方法来获取对象类型。
验证
` _stu = [Student new];
_stu.stu_id = index;
NSLog(@"beforeClass:%@", object_getClass(_stu));
[_stu addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"afterClass:%@", object_getClass(_stu));`
可以查看输出结果为:
`2018-12-30 14:34:01.761066+0800 KVO[1485:239369] beforeClass:Student`
2018-12-30 14:34:01.761533+0800 KVO[1485:239369] afterClass:NSKVONotifying_Student
自定义KVO
写一个Student的分类
Student+KVO.h
`-(void)XK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;`
Student+KVO.m
#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Student (KVO)
-(void)XK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options: (NSKeyValueObservingOptions)options context:(void *)context{
NSString* oldClassName = NSStringFromClass([self class]);
NSString* newClassName = [@"XKKVONotyfing_" stringByAppendingString:oldClassName];
const char * newName = [newClassName UTF8String];
Class newClass = objc_allocateClassPair([self class], newName, 0);
class_addMethod(newClass,@selector(setAge:), (IMP)setAge, "v@:i");
objc_registerClassPair(newClass);
object_setClass(self, newClass);
objc_setAssociatedObject(self,
(__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
void setAge(id self, SEL _cmd, int age) {
Class myClass = [self class];
object_setClass(self, class_getSuperclass([self class]));
//调用父类
id (*msgSend) (id, SEL,...) = (void *)objc_msgSend;
msgSend(self, @selector(setAge:),age);
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
id (*msgSendObj) (id, SEL,id,...) = (void *)objc_msgSend;
msgSendObj(observer,@selector(XK_observeValueForKeyPath:ofObject:change:context:),@"age",@"age",nil,nil);
//改为子类
object_setClass(self, myClass);
}
观察者中调用
<[_stu XK_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
- (void)XK_observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary)change context:(void *)context{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"ofObject:%@",object);
NSLog(@"change:%@",change);
NSLog(@"afterClass:%@", object_getClass(_stu));
}</code></pre>