KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。
下面来探讨一下KVO的本质
1.新建一个XZPerson类
#import <Foundation/Foundation.h>
@interface XZPerson : NSObject
@property(nonatomic,assign)int age;
@end
#import "XZPerson.h"
@implementation XZPerson
- (void)setAge:(int)age{
_age = age;
}
@end
#import "ViewController.h"
#import "XZPerson.h"
#import <objc/runtime.h>
@interface ViewController ()
@property(nonatomic,strong)XZPerson *person1;
@property(nonatomic,strong)XZPerson *person2;
@end
@implementation ViewController
-(void)printMethodNameOFclass:(Class)class{
unsigned int count;
//获取方法数组
Method *methodList = class_copyMethodList(class, &count);
//存储方法名
NSMutableString*methodNames = [NSMutableString string];
for (int i = 0; i<count; i++) {
//获取方法
Method meth = methodList[i];
// 获取方法名
NSString * selname =NSStringFromSelector(method_getName(meth));
[methodNames appendFormat:@"%@,",selname];
}
free(methodList);
NSLog(@"%@-%@",class,methodNames);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[XZPerson alloc] init];
self.person1.age = 10;
self.person2 = [[XZPerson alloc] init];
self.person2.age = 10;
NSLog(@"person1添加kvo之前-%@-%p-%p",object_getClass(self.person1),[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
//获取方法实现IMP
[self.person1 methodForSelector:@selector(setAge:)];
[self.person2 methodForSelector:@selector(setAge:)];
//给person对象添加观察者
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// //获取方法实现IMP
[self.person1 methodForSelector:@selector(setAge:)];
[self.person2 methodForSelector:@selector(setAge:)];
NSLog(@"person1添加kvo之后-%@-%p-%p",object_getClass(self.person1),[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
NSLog(@"添加kvo后的person的父类%@",[object_getClass(self.person1) superclass]);
///NSKVONotifying_XZPerson
//p IMP(0x100773f8e)
//[self.person1 setAge:100];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person1.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@的属性%@的值由%@变成了%@",object,keyPath,change[@"old"],change[@"new"]);
}
- (void)dealloc{
[self.person1 removeObserver:self forKeyPath:@"age"];
}
控制台打印结果如下:
2018-10-10 10:27:39.808750+0800 KVOTEST[1067:45659] person1添加kvo之前-XZPerson-0x1022fe4b0-0x1022fe4b0
2018-10-10 10:27:39.809313+0800 KVOTEST[1067:45659] person1添加kvo之后-NSKVONotifying_XZPerson-0x1026a3f8e-0x1022fe4b0
2018-10-10 10:27:39.809650+0800 KVOTEST[1067:45659] 添加kvo后的person的父类XZPerson
(lldb) p IMP(0x1022fe4b0)
(IMP) $0 = 0x00000001022fe4b0 (KVOTEST`-[XZPerson setAge:] at XZPerson.m:14)
(lldb) p IMP(0x1026a3f8e)
(IMP) $1 = 0x00000001026a3f8e (Foundation`_NSSetIntValueAndNotify)
(lldb)
由此可以看出:person1 在添加kvo监听之后,生成了一个 NSKVONotifying_XZPerson的类,其父类为XZPerson;
使用lldb查看person1的setAge方法的实现,发现添加KVO之后的person1在setAge方法内部调用了 Foundation 的`_NSSetIntValueAndNotify方法,这里涉及到Foundation框架的反编译。此方法的伪代码如下:
- (void)setAge:(int)age {
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify()
{
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key
{ // 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
总结:
- iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey:
- 父类原来的setter
- didChangeValueForKey:
- 内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
2.如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:方法