姬長信(Redy)

如何优雅姿势探究类结构(类的底层原理解析)

![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051008541750) ## 类的底层原理 ### 实例对象、类对象、元类之间的关系 直接上代码/uff0c看结果之后解释一下 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051012091802) > 思考一下这几个问题/uff1a类对象`class1`、`class2`、`class3`打印的地址分别是什么情况/uff1f 为什么`class4`是元类/uff0c`class5`是根元类/uff1f 打印结果 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051012416327) 可以看出/uff1a 类对象`class1`、`class2`、`class3`的地址是同一个/uff0c因为一个对象的类对象只有一个。 `object_getClass`获取对象的类/uff0c类对象存储的位置是哪里/uff1f在文章[笔记-runtime源码解析之让你彻底了解底层源码](https://juejin.im/post/5c9a1c935188252d9b37715a#heading-1)里讲述过/uff0c它是存在元类中/uff0c所以class4为元类/uff0c同样class5为根元类。如果还有疑问的话/uff0c可以接着往后看/uff0c或者评论里留言给笔者。 下面进行一些lldb调试/uff0c直接上图 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051025174433) - 用`person->isa`输出了`person`对象的`isa`的指向/uff0c是`ZBPerson`这个类/uff0c地址是`0x6000034232c0`/uff0c调试的输出的结果和打印输出的结果一致 - 用命令`x 0x6000034232c0`输出的是`ZBPerson`这个类内存的情况/uff0c图上说明了/uff0c前八字节指的是`isa`/uff0c为什么呢/uff1f看下面几段源码 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051030563047) `Class`是`objc_class`类型的 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051031094049) ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051031218135) 上面一段源码里可以看出/uff0c类的内部结构/uff0c前8字节为隐藏属性`isa`/uff0c接着后8字节是`superclass`/uff0c 接着是16字节的`cache`等等/uff0c具体的后面分析。 - 上面lldb调试过程中也说类/uff0c`isa`为优化过的/uff0c每次打印输出的时候/uff0c都&上了一个值`0x00007ffffffffff8`/uff0c这又是从哪里得出来的结论呢/uff1f请看下面源码/uff1a ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051031587491) ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051032113500) 嗯/uff0c讲述到这里/uff0c上面的lldb调试的过程/uff0c相信你是可以明白的/uff0c其实最终还是回到文章[笔记-runtime源码解析之让你彻底了解底层源码](https://juejin.im/post/5c9a1c935188252d9b37715a#heading-1)里的一幅图 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051032252377) ### 类结构 直接上源码 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051032456904) 上面源码/uff0c我只截图了部分代码/uff0c下面其实还有很长/uff0c这里不做说明。 `isa、superclass、cache`在上面简单描述了/uff0c这里不再重复/uff0c着重看下面/uff0c直接看下面代码 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051032553212) 这里有我们熟悉的`methods、properties、protocols`/uff0c往下走 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051033186212) ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051033308334) 这里的`list_array_tt`是一个二级指针型的/uff0c存放着我们常用的属性列表、方法列表、协议列表。 上面的截图/uff0c我也只截图了一部分/uff0c里面有个标红的`protected`/uff0c其实还有着`private`、`public`。 这又与我们日常的开发的公有属性、私有属性等等相呼应。 除了`methods、properties、protocols`/uff0c还有一个`class_ro_t`需要看一下/uff0c上源码 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051033481156) 这里有到面试题/uff0c看下面代码/uff1a ``` Class ZBClass = objc_allocateClassPair([NSObject class], "ZBClass", 0); // 添加方法--属性 NSString *name = @"name"; objc_registerClassPair(ZBClass); class_addIvar(ZBClass, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id)); id zbClass = [ZBClass alloc]; [zbClass setValue:@"Zuobian" forKey:@"name"]; NSLog(@"name == %@", [zbClass valueForKey:@"name"]); ``` 上面这段代码/uff0c能否正常运行/uff1f ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051034094553) 报错指出没有这个`key`/uff0c但是上面代码中确实已经添加/uff0c那么只是说明添加失败了/uff0c为什么呢/uff1f 如果把这两句话颠倒一下/uff0c打印查看结果 ``` class_addIvar(ZBClass, name.UTF8String, sizeof(id), log2(sizeof(id)), @encode(id)); objc_registerClassPair(ZBClass); ``` ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051034254679) 说明在注册这个函数之前添加`Ivar`是成功的/uff0c回到`class_ro_t`源码可以看出 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051034377244) `ivar_list_t`为`const`。一旦这个类创建完毕/uff0c就不能进行修改. ### MachOView查看类结构 先编译运行项目/uff0c然后找到可执行文件 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051034507210) ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051035074145) ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051035187756) 把这个可执行文件拖到**MachOView**里/uff0c下图显示 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051035356913) 然后打印出当前类的地址/uff0c通过`image list`找到首地址/uff0c通过计算器算出偏移量 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051035462808) 得到结果**0x3FE0**/uff0c然后到**MachOView**里查找 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051036009333) ### 通过lldb调试查看类结构 编译运行下面代码 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051036241664) 得到里类对象以及元类对象的地址。 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051036363601) 通过源码可以知道/uff0c我们想要得到`class_rw_t *data()`/uff0c就需要知道`class_data_bits_t bits`/uff0c而要知道`class_data_bits_t bits`/uff0c我们就需要通过类对象的地址进行指针偏移来获得。8字节+8字节+16字节--->移2位便能获取到`class_data_bits_t bits`。 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051037007723) 找到`class_rw_t *data()`后/uff0c打印出来 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051037149844) 对比下面的源码看一下 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051037301898) 对比之后/uff0c是不是我们想要的东西都在里面`class_ro_t、methods、properties、protocols`。 再进入`class_ro_t`细看一下 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051037421105) 输出的结果很明确里/uff0c当打印`baseMethodList`时/uff0c还同时给出里方法名、方法签名、所在的类以及多少行/uff1b有兴趣的读者还可以通过这种方式打印出类里的其他内容。 这一切看上去似乎很完美/uff0c给大家看一下`ZBPerson.m`文件里的内容 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051038001138) 那么问题就来了/uff0c上面的`lldb`打印只打印出了`instanceMethod`方法/uff0c那其他两个方法都去哪里了呢/uff1f nice/uff5e类方法存储在元类中/uff0c上面调试的都是类对象的结构/uff0c下面的就是类方法的调试 ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909051038135396) > 创作不易/uff0c如果想了解更多干货/uff0c请关注[掘金博客-佐笾](https://juejin.im/user/5b9b0ef16fb9a05d353c6418/posts "掘金博客-佐笾")/uff0c记得点赞哦/uff01/uff01/uff01