KVO是Key-Value Observing
的简写,称为键值观察
,用来监听对象属性的变化
。markdown
咱们以IFPerson
类为例研究KVO
的使用。函数
KVO
监听- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person.name = @"kvo";
NSLog(@"class = %@",object_getClass(self.person));
//对person的name添加kvo监听
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"class = %@",object_getClass(self.person));
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person.name = [NSString stringWithFormat:@"%@ +",self.person.name];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@" === %@",change);
}
}
-(void)dealloc {
//移除监听
[self.person removeObserver:self forKeyPath:@"name"];
}
复制代码
运行代码点击屏幕效果以下spa
KVO
监听若是想要手动触发KVO
监听,须要重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
函数,该函数默认返回YES,表示自动触发,返回NO,则表示手动触发
。对于本文,咱们判断下key == name
则手动触发,不然自动触发。修改代码以下3d
//在`IFPerson.m`中
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
-(void)setName:(NSString *)name {
[self willChangeValueForKey:name];
_name = name;
[self didChangeValueForKey:name];
}
复制代码
从上述代码中,咱们能够看到,要实现手动触发KVO
,有两个步骤须要实现指针
步骤一
:重写+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
函数,根据须要返回NO步骤二
:对于监听的对象属性发生改变时,须要在改变先后调用willChangeValueForKey:
和didChangeValueForKey:
KVO
底层原理探究从上述中咱们能够看到咱们能够根据KVO
监听属性的变化,那么在底层是怎么实现的呢?code
KVO
监听先后对象的变化咱们在监听name
先后获取person
的class以及其指针地址
看下是否有变化,代码以下orm
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person.name = @"kvo";
NSLog(@"class = %@ address = %p",object_getClass(self.person),self.person);
//对person的name添加kvo监听
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"class = %@ address = %p",object_getClass(self.person),self.person);
}
复制代码
打印结果以下从上图中咱们看到,
self.person
的class
先后发生了改变,可是对象地址并无改变。咱们再经过获取isa
来验证下。server
KVO
监听后,对象的class即isa指向发生了改变
,由IFPerson
变成了NSKVONotifying_IFPerson
,可是对象的地址没有改变
。NSKVONotifying_IFPerson
是什么?咱们知道了在进行KVO
监听后class
变成了NSKVONotifying_IFPerson
,那么NSKVONotifying_IFPerson
从何而来?下面咱们看下NSKVONotifying_IFPerson
的父类是谁。对象
因为superClass
是隐藏属性,在外部没法访问,由于咱们定义一个结构体,并强转下类型来得到superClass
,代码以下继承
struct cw_objc_class {
Class _Nonnull isa;
Class _Nullable super_class;
};
// 强转一下类型
struct cw_objc_class *newClass = (__bridge struct cw_objc_class *)object_getClass(self.person);
NSLog(@"superClass = %@",newClass->super_class);
复制代码
从打印结果来看
NSKVONotifying_IFPerson
的父类居然是IFPerson
。
NSKVONotifying_IFPerson
类实现了哪些方法?既然runtime
在进行KVO
监听的时候派生了NSKVONotifying_IFPerson
类,那么NSKVONotifying_IFPerson
类跟IFPerson
类实现方法有什么区别呢?咱们打印下监听先后两个类的实现方法区别
#pragma mark - 遍历方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
NSLog(@"监听以前方法:");
[self printClassAllMethod:object_getClass(self.person)];
//对person的name添加kvo监听
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"监听以前方法:");
[self printClassAllMethod:object_getClass(self.person)];
复制代码
打印结果以下从打印监听先后方法列表咱们能够看到
NSKVONotifying_IFPerson
类有以下变化
setter
方法,如setName:isa
指针指向的地址,由原来的IFPerson
指向了NSKVONotifying_IFPerson
dealloc
方法_isKOA
标识符isa
指针什么时候从新指向IFPerson
?既然isa
指针在被监听后指向了NSKVONotifying_IFPerson
类,那么什么时候从新指向IFPerson
呢?答案是在移除监听的时候
。为了验证,咱们在dealloc
中打印下监听先后isa
指针的变化。
KVO
总结KVO
在监听属性后,runtime会根据对象的类型生成一个NSKVONotifying_XXX(XXX是原来的类)
的派生类
,该派生类NSKVONotifying_XXX
继承于原来的类
NSKVONotifying_XXX
重写被监听属性的setter
方法,可是赋值
的时候经过重写的setter
内部消息转发仍是经过原来类的setter
方法来实现KVO
在移除监听时将isa
指针指向地址由NSKVONotifying_XXX
派生类从新指向原来类