KVO本质的推导

阅读过很多文章都只是阐述了KVO的本质,并没有辅以代码推导过程,所以本文的主要目的是梳理正向推导本质的过程

本文主要分为两部分

  1. 正向推导KVO本质
  2. 无法推导的进行反向验证

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
复制代码

监听方法没有被调用

小记:之前一直在简书,写的文章也多为随笔,随手记录不成文。这篇是真正意义上第一次系统的写文章,诸多不足请各位见谅~多提宝贵意见!也希望自己能坚持写下去~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值