当前位置: 首页 > news >正文

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

监听方法没有被调用

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

相关文章:

  • canny算子求图像边缘,edgebox那部分
  • 28.week4
  • SQL中Group By的使用
  • 大数据就业前景怎么样?
  • 商品期货趋势交易策略
  • 版本控制工具Git工具快速入门-Linux篇
  • 计算机网络基础:这是一份详细 HTTP 学习指南
  • 避免MySQL出现重复数据处理方法
  • 强如 Disruptor 也发生内存溢出?
  • tomcat介绍和安装
  • 为什么大部分码农做不了软件架构师?
  • WebSocket于HTTP 、WebSocket与Socket的区别
  • WPF中Binding使用StringFormat格式化字符串方法
  • [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式...
  • P2P
  • 2019年如何成为全栈工程师?
  • Asm.js的简单介绍
  • Consul Config 使用Git做版本控制的实现
  • flutter的key在widget list的作用以及必要性
  • HTTP 简介
  • jquery cookie
  • PermissionScope Swift4 兼容问题
  • Puppeteer:浏览器控制器
  • tab.js分享及浏览器兼容性问题汇总
  • XForms - 更强大的Form
  • 半理解系列--Promise的进化史
  • 大整数乘法-表格法
  • 多线程事务回滚
  • 解析 Webpack中import、require、按需加载的执行过程
  • 精彩代码 vue.js
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 使用agvtool更改app version/build
  • 微信开放平台全网发布【失败】的几点排查方法
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 追踪解析 FutureTask 源码
  • 你对linux中grep命令知道多少?
  • Spring Batch JSON 支持
  • #HarmonyOS:Web组件的使用
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (C#)一个最简单的链表类
  • (C++)八皇后问题
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (二)springcloud实战之config配置中心
  • (转)编辑寄语:因为爱心,所以美丽
  • (转)程序员技术练级攻略
  • .gitignore文件---让git自动忽略指定文件
  • .net MySql
  • .net项目IIS、VS 附加进程调试
  • /etc/sudoers (root权限管理)
  • [ C++ ] STL---string类的模拟实现
  • [Android] Amazon 的 android 音视频开发文档
  • [AutoSar]BSW_OS 01 priority ceiling protocol(PCP)
  • [AX]AX2012 AIF(四):文档服务应用实例
  • [C++]:for循环for(int num : nums)