阅读过很多文章都只是阐述了KVO的本质,并没有辅以代码推导过程,所以本文的主要目的是梳理正向推导本质的过程
本文主要分为两部分
- 正向推导KVO本质
- 无法推导的进行反向验证
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
KVO的基本使用
KVODemoClass 测试类
@interface KVODemoClass : NSObject
@property (assign, nonatomic) int number;
@property (assign, nonatomic) int reference; // 参照属性 无用
@end
复制代码
我们创建两个实例对象 对 demo1 这个对象number属性进行键值监听, demo2 不做处理
此时进入lldb 分别打印出两个对象的isa指针如下
(lldb) p self.demo1->isa
(Class) $1 = NSKVONotifying_KVODemoClass
(lldb) p self.demo2->isa
(Class) $2 = KVODemoClass
复制代码
通过isa指针我们可以看出进行键值监听的 demo1 的实际指向已经变成 NSKVONotifying_KVODemoClass 而我们知道一个实例对象的isa指针指向的应该是其类对象,由此我们可以得出使用KVO后Runtime创建一个 NSKVONotifying_原类名 的派生类,而我们在使用的时候是毫无感知的。
那么 NSKVONotifying_KVODemoClass 的内部情况是怎么样的呢?
我们来输出一下NSKVONotifying_KVODemoClass和KVODemoClass类对象中的方法列表和属性列表 由于本文重点在KVO 所以runtime的知识点就不赘述了,直接附上代码
- (void)printMethodNamesOfClass:(Class)cls
{
unsigned int count;
unsigned int propertyCount;
Method *methodList = class_copyMethodList(cls, &count);
objc_property_t *propertyList = class_copyPropertyList(cls, &propertyCount);
NSMutableString *methodNames = [NSMutableString string];
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
NSMutableString *propertyNames = [NSMutableString string];
for (int i = 0; i < propertyCount; i++) {
objc_property_t ivar = propertyList[i];
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(ivar)];
[propertyNames appendString:propertyName];
[propertyNames appendString:@", "];
}
free(methodList);
free(propertyList);
NSLog(@"%@ %@", cls, methodNames);
NSLog(@"%@ %@", cls, propertyNames);
}
复制代码
结果如下
KVODemo[4935:1065888] NSKVONotifying_KVODemoClass setNumber:, class, dealloc, _isKVOA,
KVODemo[4935:1065888] NSKVONotifying_KVODemoClass
KVODemo[4935:1065888] KVODemoClass setReference:, reference, number, setNumber:,
KVODemo[4935:1065888] KVODemoClass number, reference,
复制代码
派生类没有产生新的属性而是对set方法进行了重写,并且没有被监听的reference属性的set方法不会重写。
那么被重写的set方法有什么变化呢?
NSLog(@"添加监听之前%p------%p",[self.demo1 methodForSelector:@selector(setNumber:)],[self.demo2 methodForSelector:@selector(setNumber:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.demo1 addObserver:self forKeyPath:@"number" options:options context:@"123"];
NSLog(@"添加监听之后%p------%p",[self.demo1 methodForSelector:@selector(setNumber:)],[self.demo2 methodForSelector:@selector(setNumber:)]);
复制代码
打个断点进入lldb
KVODemo[5093:1124218] 添加监听之前0x10a5c2a70------0x10a5c2a70
KVODemo[5093:1124218] 添加监听之后0x10a9736c4------0x10a5c2a70
(lldb) p (IMP)0x10a5c2a70
(IMP) $0 = 0x000000010a5c2a70 (KVODemo`-[KVODemoClass setNumber:] at KVODemoClass.m:12)
(lldb) p (IMP)0x10a9736c4
(IMP) $1 = 0x000000010a9736c4 (Foundation`_NSSetIntValueAndNotify)
复制代码
很神奇的发现添加监听后 setNumber方法的实际指向已经变成了 NSSetIntValueAndNotify 函数 当然这是由于我们的属性类型是int 附上一张图
不同类型的属性对应不同的函数。
由于技术原因本菜的正向推导只能进行到这一步了,下面内容是反向验证,也希望有大神能给予正向推导的思路
验证内容:
- 1.NSSetIntValueAndNotify中方法的调用为
- 1.1 willChangeValueForKey
- 1.2 setNumber
- 1.3 didChangeValueForKey
- 1.4 在1.3的调用过程中会调用监听者的
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
复制代码
重写KVODemoClass的以下方法
-(void)setNumber:(int)number
{
_number = number;
NSLog(@"setNumber:");
}
- (void)willChangeValueForKey:(NSString *)key
{
NSLog(@"willChangeValueForKey---begin");
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey---end");
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(@"didChangeValueForKey---begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey---end");
}
复制代码
控制台输出为
KVODemo[5265:1173234] willChangeValueForKey---begin
KVODemo[5265:1173234] willChangeValueForKey---end
KVODemo[5265:1173234] setNumber:
KVODemo[5265:1173234] didChangeValueForKey---begin
KVODemo[5265:1173234] 监听到<KVODemoClass: 0x60400001a5d0>的number属性值改变了 - {
kind = 1;
new = 2;
old = 1;
} - 123
KVODemo[5265:1173234] didChangeValueForKey---end
复制代码
- 2.单独调用didChangeValueForKey不会触发监听
我们注释掉willChangeValueForKey的实现,控制台输出
KVODemo[5317:1175253] setNumber:
KVODemo[5317:1175253] didChangeValueForKey---begin
KVODemo[5317:1175253] didChangeValueForKey---end
复制代码
监听方法没有被调用
小记:之前一直在简书,写的文章也多为随笔,随手记录不成文。这篇是真正意义上第一次系统的写文章,诸多不足请各位见谅~多提宝贵意见!也希望自己能坚持写下去~