KVO的全称是key-value Observng,也叫作“键值监听”,一般用来监听某个对象的某个属性值的变化。下面使用一个简单的例子来回顾一下KVO的用法。面试
@interface XLPerson : NSObject
@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)int age;
@end
复制代码
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
self.person.age = 10;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n keyPath:%@, \n object:%@, \n change:%@", keyPath, object, change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
}
@end
复制代码
2019-11-13 14:52:49.960452+0800 TestFont[52476:1429894]
keyPath:age,
object:<XLPerson: 0x6000039bec00>,
change:{
kind = 1;
new = 20;
old = 10;
}
复制代码
结合以前对NSObject底层的学习咱们知道,实例对象的isa指针指向它的类对象,那么上文例子中的person对象的isa指针应该指向它的类对象XLPerson,为了作对比,咱们增长一个person2对象:bash
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
self.person.age = 10;
self.person2 = [[XLPerson alloc] init];
self.person2.age = 30;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n keyPath:%@, \n object:%@, \n change:%@", keyPath, object, change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
}
@end
复制代码
在touchesBegan方法中添加断点,而后咱们使用LLDB命令来对代码进行调试框架
(lldb) p self.person->isa
(Class) $1 = NSKVONotifying_XLPerson
(lldb) p self.person2->isa
(Class) $2 = XLPerson
(lldb)
复制代码
这时候会发现添加了Observer后的person对象的isa指针不是指向XLPerson,而是指向一个新的类对象NSKVONotifying_XLPerson,而person2对象因为没有添加Observer,因此它的isa指针指向的是类对象XLPerson。函数
因为咱们并无建立过NSKVONotifying_XLPerson类,因此NSKVONotifying_XLPerson是在运行时动态生成的一个新的类,新类生成以后,又将person的isa指针指向了新的类对象。学习
为了了解NSKVONotifying_XLPerson的内部构造,咱们自定义一个方法来打印Class的方法列表和superClass测试
- (void)descriptionOfClass:(Class)cls{
NSLog(@"------------ %@ -----------", NSStringFromClass(cls));
NSLog(@"%@ superClass ----> %@", NSStringFromClass(cls),NSStringFromClass(class_getSuperclass(cls)));
unsigned int count;
Method *methondList = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i++) {
Method method = methondList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"%@ ----> %@", NSStringFromClass(cls), methodName);
}
free(methondList);
}
复制代码
修改示例中的代码,打印出person和person1的方法列表和superClassui
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
self.person.age = 10;
self.person2 = [[XLPerson alloc] init];
self.person2.age = 20;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self descriptionOfClass:object_getClass(self.person)];
NSLog(@"\n");
[self descriptionOfClass:object_getClass(self.person2)];
}
复制代码
注意:因为对象的实例方法都存放在类对象的methonList中,因此此处咱们须要经过object_getClass方法拿到person和person1对象的类对象,而后经过遍历类对象的方法列表打印出具体的方法名称。 object_getClass方法若是传递过去一个示例对象,那么会返回对应的类对象,若是传递过去一个类对象,会返回对应的元类对象。编码
运行程序,获得如下运行结果atom
从图中能够看出,person对象因为加了KVO监听,因此它的类对象变成了NSKVONotifying_XLPerson,而NSKVONotifying_XLPerson对象的superClass是XLPerson,说明NSKVONotifying_XLPerson是XLPerson的子类。spa
在NSKVONotifying_XLPerson方法列表中主要有4个方法,setAge:、class、dealloc和_isKVOA,下面咱们就来一一分析这四个方法。
- (void)setAge:(int)age{
//调用Foundationf框架中的_NSSetIntValueAndNotify方法
[self _NSSetIntValueAndNotify];
}
- (void)_NSSetIntValueAndNotify{
//将要修改age的值
[self willChangeValueForKey:@"age"];
//调用父类的setAge方法去修改age的值
[super setAge:age];
//完成修改age的值,而且执行observeValueForKeyPath方法
[self didChangeValueForKey:@"age"];
}
复制代码
- (Class)class{
return [XLPerson class];
}
复制代码
上文介绍了NSKVONotifying_XLPerson对象中的几个主要的方法,如今咱们就来还原一下NSKVONotifying_XLPerson对象完整的内部结构。
首先,NSKVONotifying_XLPerson是Class类型的对象,因此它内部确定拥有isa指针和superClass指针,由此能够获得NSKVONotifying_XLPerson的结构以下:
结合isa指针的指向能够获得如下结构:
由此也能够获得NSKVONotifying_XLPerson的伪代码以下
@interface NSKVONotifying_XLPerson : XLPerson
@end
@implementation NSKVONotifying_XLPerson
- (void)setAge:(int)age{
//调用Foundationf框架中的_NSSetIntValueAndNotify方法
[self _NSSetIntValueAndNotify];
}
- (void)_NSSetIntValueAndNotify{
//将要修改age的值
[self willChangeValueForKey:@"age"];
//调用父类的setAge方法去修改age的值
[super setAge:age];
//完成修改age的值,而且执行observeValueForKeyPath方法
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
//触发observeValueForKeyPath方法
[self observeValueForKeyPath:@"age" ofObject:self change:nil context:nil];
}
- (void)dealloc{
//释放操做
}
- (Class)class{
return [XLPerson class];
}
- (BOOL)_isKVOA{
return YES;
}
@end
复制代码
KVC,俗称“键值编码”,全称是“Key Value Coding”,它是一种能够直接经过字符串的名称(Key)来访问类属性的机制,而不是经过调用Setter或者Getter方法来进行访问。
KVC的经常使用方法以下
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
复制代码
KVC有两种赋值和取值方法,下面咱们经过一个简单的例子来了解一下。首先建立XLPerson类和XLStudent类
@interface XLStudent : NSObject
@property(nonatomic, assign)int num;
@end
@interface XLPerson : NSObject
@property(nonatomic, strong)XLStudent *student;
@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign)int age;
@end
复制代码
而后经过KVC来设置XLPerson和XLStudent的属性的值,以下
- (void)viewDidLoad{
[super viewDidLoad];
XLPerson *person = [[XLPerson alloc] init];
[person setValue:[[XLStudent alloc] init] forKey:@"student"];
[person setValue:@10 forKey:@"age"];
[person setValue:@"张三" forKey:@"name"];
[person setValue:@20 forKeyPath:@"student.num"];
NSNumber *age = [person valueForKey:@"age"];
NSString *name = [person valueForKey:@"name"];
NSNumber *num = [person valueForKeyPath:@"student.num"];
NSLog(@"%@, %@, %@",age,name,num);
}
复制代码
最后获得结果age=10、name=张三、num=20,因而可知,经过KVC确实能够修改对象中的属性。
使用KVC除了能够修改属性,也能够修改为员变量的值,在XLPerson中增长以下成员变量
@interface XLPerson : NSObject{
int _height;
int _weight;
}
@end
复制代码
而后使用KVC进行赋值
XLPerson *person = [[XLPerson alloc] init];
[person setValue:@30 forKey:@"_height"];
[person setValue:@40 forKeyPath:@"_weight"];
NSLog(@"%@, %@",person->_height,person->_weight);
复制代码
最后能够发现KVC确实也能修改为员变量的值。
同时,经过上面的代码咱们能够看出两种赋值和取值方法的区别。
[[person valueForKey:@"student"] valueForKey:@"num"];
复制代码
其实经过setValue:forkey方法给对象的属性赋值,主要通过如下几个流程
对应流程图以下:
经过valueForKey:方法取值,流程以下:
流程图以下:
经过对KVO的探索,咱们知道,给对象的某个属性添加KVO监听,实际上是动态建立了一个此类的子类,而后将对象的isa指针指向新生成的类,最后经过重写属性的setter方法来添加监听。那么若是使用KVO来对属性或者成员变量进行赋值,会触发KVO监听吗?咱们经过一个简单的例子来测试一下
仍是使用上文的XLPerson对象
- (void)viewDidLoad{
[super viewDidLoad];
self.person = [[XLPerson alloc] init];
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self.person addObserver:self forKeyPath:@"_height" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"\n keyPath:%@, \n object:%@, \n change:%@", keyPath, object, change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person setValue:@10 forKey:@"age"];
[self.person setValue:@20 forKeyPath:@"_height"];
}
复制代码
运行代码,点击屏幕能够看到以下打印信息
经过KVC不论是设置属性的值仍是成员变量的值,都会触发KVO监听,说明在KVC内部确实会在给属性或成员变量赋值的时候,会经过相似调用didChangeValueForKey方法来触发KVO监听。
在修改变量先后手动调用willChangeValueForKey:和didChangeValueForKey:方法
[self willChangeValueForKey:name];
_name = @"xxx";
[self didChangeValueForKey:name];
复制代码
直接修改为员变量的值不会触发KVO,由于没有触发setter方法。
会触发KVO。
参考上文流程图
参考上文流程图
以上内容纯属我的理解,若是有什么不对的地方欢迎留言指正。
一块儿学习,一块儿进步~~~