说说背景,研究下面的代码时,KVO后[obj class]与object_getClass(id obj)的结果竟会不一致?

    PersonModel *aPersonModel = [[PersonModel alloc] init];
aPersonModel.name=@"lisi";
NSLog(@"之前%@ %@",[aPersonModel class],object_getClass(aPersonModel));
[aPersonModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"之后%@ %@",[aPersonModel class],object_getClass(aPersonModel));
aPersonModel.name=@"zhangsan"; //[aPersonModel removeObserver:self forKeyPath:@"name"];

查看 NSObject 底层代码

+ (Class)class {
return self;
} - (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}

很明显,实例方法 class 内部调用和 object_getClass 毫无区别,都是获得对象的 isa 指针。类方法直接返回的本身。那么为啥 KVO 后[obj class]与object_getClass(id obj)的结果竟会不一致?

打印一下 KVO 后,NSKVONotifying_PersonModel 里的方法。发现系统内部,重写了 class 方法,直接返回的 KVO 之前的类对象。

    Class cls =  object_getClass(aPersonModel);
[self printMethodList:cls];
- (void)printMethodList:(Class)cls
{ unsigned int outCount;
Method* methods = class_copyMethodList(cls,&outCount); for (int i = 0; i < outCount ; i++)
{
SEL name = method_getName(methods[i]);
NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"selName : %@",strName);
}
}

结果打印:

2021-06-04 14:40:02.434062+0800 MsgSendTest[70021:7491894] selName : setName:
2021-06-04 14:40:02.434233+0800 MsgSendTest[70021:7491894] selName : class
2021-06-04 14:40:02.434368+0800 MsgSendTest[70021:7491894] selName : dealloc
2021-06-04 14:40:02.434487+0800 MsgSendTest[70021:7491894] selName : _isKVOA

重新回归KVO的原理:

1.比如原先实例 aPersonModel 的isa指针指向的是 PersonModel,那么当你在第一次调用过

[aPersonModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

方法后,runtime 会创建一个新的类,类名以NSKVONotifing开头叫 NSKVONotifying_PersonModel,同时更改实例 aPersonModel 的 isa 的指针,将其指向 NSKVONotifying_PersonModel 。

2.在 NSKVONotifying_PersonModel 中重写观察的属性 name 的 setter 方法 setName ,并在它里面调用

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context

方法。这样当改变属性 name 的值时,外面就会得到通知。

3.在 NSKVONotifying_PersonModel 中重写 - (Class) class 方法,返回原先的 isa 指向的类(在这个例子中就是 PersonModel )。这就是为什么[aPersonModel class]与object_getClass(aPersonModel) 返回的结果不一致的原因。

KVO后[obj class]与object_getClass(id obj)的结果竟会不一致?的更多相关文章

  1. VB.NET中使用Linq TO SQL添加数据后获得自增长列ID

    VB.NET中使用Linq TO SQL添加数据后获得自增长列ID: Dim tempOrdre As New Order With { .CustomerID = cmbCustomerName.S ...

  2. __getattribute__(self, obj) 这个方法中的obj这个参数

    class Itcast(object): def __init__(self, subject1): self.subject1 = subject1 print("^^^^^^^---- ...

  3. R = [obj for obj in recs[imagename] if obj['name'] == classname] KeyError: '007765'

    在用RFBNet做测试的时候,好几次总是遇到 R = [obj for obj in recs[imagename] if obj['name'] == classname]  KeyError: ' ...

  4. MSSQL2005后版本插入数据返回ID的新写法

    例子: INSERT VolunteerSound_Table (Title,ArticleContent)OUTPUT Inserted.ID VALUES ('FirstVal','bbbbb') ...

  5. 使用mybatis插入自增主键ID的数据后返回自增的ID

    在开发中碰到用户注册的功能需要用到用户ID,但是用户ID是数据库自增生成的,这种情况上网查询后使用下面的方式配置mybatis的insert语句可以解决: <insert id="in ...

  6. DedeCMS清空删除所有文档后新建文档信息ID从1开始

    方法一.登录织梦后台,找到系统->系统设置->SQL命令行工具 分别运行以下命令: 清除表中的数据,删除所有文章: truncate table `dede_arctiny`; trunc ...

  7. Mybatis 插入后返回数据库自动增长ID

    MySQL和MSSQL返回主键方法 在personMap.xml中 <insert id="addPerson" parameterType="orm.Person ...

  8. java中String.valueOf(obj)、(String)obj与obj.toString()有什么区别

    方法1:采用 Object.toString()方法 在这种使用方法中,因为java.lang.Object类里已有public方法.toString(),所以对任何严格意义上的java对象都可以调用 ...

  9. [转] Java 插入表记录后得到自增的id (附3种方法代码)

    转自:https://blog.csdn.net/yaerfeng/article/details/7231093 在MySQL中,使用auto_increment类型的id字段作为表的主键,并用它作 ...

随机推荐

  1. JAVAEE_Servlet_15_HttpServletRequest中常用方法(二)

    HttpServletRequest中常用方法 ## HttpServletRequest中的第二类方法 获取各种路径 和 IP地址 * 获取路径和地址 - 获取ServletContext上下文对象 ...

  2. mooc人大单元测试2

    1 单选(2分) 下列选项中不是关系数据库基本特征的是(  ). A. 不同的列应有不同的数据类型 B. 不同的列应有不同的列名 C. 与行的次序无关 D. 与列的次序无关 2 单选(2分) 关系代数 ...

  3. Ionic5路由跳转传值复用

    1. 路由技术 ( 详细记录 ) 是笔记不是博文,觉得写的不够详细的可以使用Ctrl + W组合键 路由跳转页面 1. HTML 中使用 routerLink 属性路由进行跳转,传值时使用 query ...

  4. 『动善时』JMeter基础 — 6、使用JMeter发送一个最基础的请求

    目录 步骤1:创建一个测试计划 步骤2:创建线程组 步骤3:创建取样器 步骤4:创建监听器 步骤5:完善信息 步骤6:保存测试计划 步骤7:查看结果 总结:JMeter测试计划要素 当我们第一次打开J ...

  5. 路由器逆向分析------在Linux上安装IDA Pro

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/69665905 01.在Linux系统上安装Linux版本的IDA Pro Linu ...

  6. Windows核心编程 第九章 线程与内核对象的同步(下)

    9.4 等待定时器内核对象 等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象.它们通常用来在某个时间执行某个操作. 若要创建等待定时器,只需要调用C r e a t e Wa i ...

  7. node-util

    Node.js 常用工具 util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心JavaScript 的功能 过于精简的不足. util.inherits util.inherit ...

  8. Hack The Box - Archetype

    攻略的话在靶场内都有,也有官方的攻略,我作为一个技术小白,只是想把自己的通关过程记录下来,没有网站内大佬们写得好 我们获得了一个IP: 尝试访问了一下,应该不存在web页面: 对常规端口进行一个扫描: ...

  9. Postman(接口自动化测试)

    1.Postman 接口测试参数化可能大家都非常的熟悉,但是很多人很难处理参数化后如何断言的问题,特别是当参数中出现中文时,很容易导致在 Runner 页面引入外部文件时导致中文乱码的问题,今天这篇文 ...

  10. 实施CRM系统后 企业客户服务的改变

    通过实施CRM客户管理系统,企业的竞争力和盈利能力得到大幅提高.在这个过程中,客户服务不仅能够持续的满足客户的需求,还能够促进客户与企业建立长期的互惠互利的良好客户关系,这也为企业赢得了更多的商机和利 ...