- 引言
上一篇讲到了内存偏移的知识和操作,接下来内存偏移将在本文用到具体的示例。我们对对象的探究已经了解了对象的底层结构,isa的走向和对象的继承链。本文将还原探究类内部结构的过程。 - 类的探索
- 001-OC对象原理探究 - alloc这篇文章中,我们用到了objc源码环境调试。本文我们也在此基础上,探究类的结构。我们在对象的底层结构探索的时候,发现了
类Class的底层为typedef struct objc_class *Class;也就是说,Class是一个结构体指针的别名。
具体寻找步骤:
1、objc源码调试环境工程,搜索Class {,在结果中我们得到runtime.h中的class结构:
2、这似乎就是我们要找的Class底层,但是看到`#if !_OBJC2以及OBJC2_UNAVAILABLE,才知道,整个结构体struct objc_class并不适用于objc2中(本文调试的环境的是objc4_818_2)<br />3、那么我们怎么找到正确的Class呢?请看框起来的注释/ UseClassinstead of`struct objc_class*/
源码如下:">一、寻找objcclass
在001-OC对象原理探究 - alloc这篇文章中,我们用到了objc源码环境调试。本文我们也在此基础上,探究类的结构。我们在对象的底层结构探索的时候,发现了类Class的底层为typedef struct objc_class *Class;也就是说,Class是一个结构体指针的别名。
具体寻找步骤:
1、objc源码调试环境工程,搜索Class {,在结果中我们得到runtime.h中的class结构:
2、这似乎就是我们要找的Class底层,但是看到`#if !_OBJC2以及OBJC2_UNAVAILABLE,才知道,整个结构体struct objc_class并不适用于objc2中(本文调试的环境的是objc4_818_2)<br />3、那么我们怎么找到正确的Class呢?请看框起来的注释/ UseClassinstead of`struct objc_class*/
源码如下: - 二、bits
上图的objc_class内部可知bits在Class superclass和cache_t cache之后。我们调试得到bits,需要上篇文章提到的内存偏移来得到。因此,我们需要知道偏移了多少字节,接下来我们开始探索Class superclass和cache_t cache的内存大小。
1、superclass是Class指针类型,因此superclass占8字节
2、cache_t为结构体类型,其内部结构如下:(由于我们需要知道结构体内存的大小,只需要知道其成员变量的大小即可,cache_t内部的static和方法函数均不影响结构体内存大小,因此以下源码为简化后的cache_t):
- 001-OC对象原理探究 - alloc这篇文章中,我们用到了objc源码环境调试。本文我们也在此基础上,探究类的结构。我们在对象的底层结构探索的时候,发现了
- 总结
1、探索类的结构是一个漫长而复杂的过程,有些地方卡在那里,如果不转换思路,将进入死胡同。对类的探索,应该多借鉴前辈的肩膀,偶尔用上帝视角去解决遇到的难题。
2、存储位置:类的首地址+0x20得到bitsbits->data()得到class_rw_t
a)获取类的属性(@property标记):bits中的class_rw_t中的properties()
b)获取成员变量(类大括号内的声明):bits中的class_rw_t中的ro()
c)获取实例方法(也叫对象方法-()):bits中的class_rw_t中的methods(),每一项需要加.big()来打印
d)获取类方法(+()):元类中的bits->class_rw_t->methods(),每一项需要加.big()来打印
引言
上一篇讲到了内存偏移的知识和操作,接下来内存偏移将在本文用到具体的示例。我们对对象的探究已经了解了对象的底层结构,isa的走向和对象的继承链。本文将还原探究类内部结构的过程。
类的探索
一、寻找objcclass
在001-OC对象原理探究 - alloc这篇文章中,我们用到了objc源码环境调试。本文我们也在此基础上,探究类的结构。我们在对象的底层结构探索的时候,发现了类Class的底层为typedef struct objc_class *Class;也就是说,Class是一个结构体指针的别名。
具体寻找步骤:
1、objc源码调试环境工程,搜索Class {,在结果中我们得到runtime.h中的class结构:

2、这似乎就是我们要找的Class底层,但是看到`#if !_OBJC2以及OBJC2_UNAVAILABLE,才知道,整个结构体struct objc_class并不适用于objc2中(本文调试的环境的是objc4_818_2)<br />3、那么我们怎么找到正确的Class呢?请看框起来的注释/ UseClassinstead of`struct objc_class */
源码如下:
/// An opaque type that represents an Objective-C class.typedef struct objc_class *Class;/// Represents an instance of a class.struct objc_object {Class _Nonnull isa OBJC_ISA_AVAILABILITY;};/// A pointer to an instance of a class.typedef struct objc_object *id;
在此源码中,我们还得到一个信息:OC中id类型的底层竟然是typedef struct objc_object *id;,这就是为什么我们在定义id类型的变量时,不加*号的原因。
探索源码真的能学到很多!!!
4、搜索框输入struct objc_class,其中objc_runtime-new.h中的结果就是我们想要的,结果如下:
二、bits
上图的objc_class内部可知bits在Class superclass和cache_t cache之后。我们调试得到bits,需要上篇文章提到的内存偏移来得到。因此,我们需要知道偏移了多少字节,接下来我们开始探索Class superclass和cache_t cache的内存大小。
1、superclass是Class指针类型,因此superclass占8字节
2、cache_t为结构体类型,其内部结构如下:(由于我们需要知道结构体内存的大小,只需要知道其成员变量的大小即可,cache_t内部的static和方法函数均不影响结构体内存大小,因此以下源码为简化后的cache_t):
struct cache_t {private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask;union {struct {explicit_atomic<mask_t> _maybeMask;#if __LP64__uint16_t _flags;#endifuint16_t _occupied;};explicit_atomic<preopt_cache_t *> _originalPreoptCache;};}
3、分析
a)、_bucketsAndMaybeMask是explicit_atomic的泛型变量,因此实际大小为泛型的大小,即uintptr_t的大小,uintptr_t的源码为:typedef unsigned long uintptr_t;因此占8字节。
b)、union为共用体,内存大小为最大的成员的大小。
1)struct中,_maybeMask为mask_t,源码为typedef uint32_t mask_t;占4字节,uint16_t大小为2字节。结构体最大占用内存4 + 2 + 2 = 8字节
2)_originalPreoptCache为preopt_cache_t *,结构体指针类型,我们知道指针类型的大小为8字节。
3)其实换个角度来看,union中,我们只需要看_originalPreoptCache的大小即可知道union占用大小为8字节。
4、cache_t所占内存大小为:16字节。
到此为止,我们只需要或许到类的首地址后,将其平移isa:8 + superclass:8 + cache_t:16 = 32字节才能得到bits。简化后的objc_class源码如下:
struct objc_class : objc_object {Class ISA; //8字节Class superclass;// 8字节cache_t cache; // 16字节class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags}
下面我们开始获取bits:
1、QLPerson设计如下:
@interface QLPerson : NSObject{NSString *fullName;}@property (nonatomic,copy) NSString *nickName;@property (nonatomic,assign) NSInteger age;- (void)test1;+ (void)test1;@end
2、对QLPerson类进行lldb调试
3、p *$2->data()为objc_class中的方法
class_rw_t *data() const {return bits.data();}

但是似乎未能得到我们想要的东西。换种思路继续
4、我们继续objc_class向下翻找,治世之尊没有找到能看到类的属性和方法的关键词。后来看到bits后面的注释,我们要的东西是否在class_rw_t里。点进去终于看到了
const method_array_t methods() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;} else {return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};}}const property_array_t properties() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;} else {return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};}}const protocol_array_t protocols() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;} else {return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};}}
methods(),properties(),protocols()这正是我们日思夜想的东西嘛。lldb调试如下:
由图可知:我们的@property声明的属性,均存在property_list_t中,用上图的lldb调试可以找到属性的值。但是问题来了,我们的方法和成员变量均未得到,它们放在哪儿呢?
补充:properties()返回类型为property_array_t,继承自list_array_tt,源码如下:
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {};
说明:关于二维数组容器list_array_tt的知识请移步这篇文章
5、properties()探索结束,未能达到我们的目的,我们接着探索methods()
说明:我们用探索properties的方式来探索methods最终得到了该类的实例方法,getter setter方法,达到部分目的,因为,我们的+(void)test2还未出现。
6、methods()探索结束我们仍未找到类方法和成员变量ivar的存储位置,我们接着往下探索,在class_rw_t内找到一个ro()方法,源码如下:
const class_ro_t *ro() const {auto v = get_ro_or_rwe();if (slowpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;}return v.get<const class_ro_t *>(&ro_or_rw_ext);}
class_ro_t内部部分源码为:
struct class_ro_t {void *baseMethodList;const ivar_list_t * ivars;
这个ivars操作如下:
到此为止,我们拿到了成员变量的存储位置已经搞清楚。
7、类方法探索,换种思路,实例方法也叫做对象方法。类方法似乎与对象无关,那么它是否在元类里呢?按照这个思路,我们对QLPerson的元类进行上面的查找操作:
由图可得到类方法存储在元类中。
总结
1、探索类的结构是一个漫长而复杂的过程,有些地方卡在那里,如果不转换思路,将进入死胡同。对类的探索,应该多借鉴前辈的肩膀,偶尔用上帝视角去解决遇到的难题。
2、存储位置:
类的首地址+0x20得到bits
bits->data()得到class_rw_t
a)获取类的属性(@property标记):bits中的class_rw_t中的properties()
b)获取成员变量(类大括号内的声明):bits中的class_rw_t中的ro()
c)获取实例方法(也叫对象方法-()):bits中的class_rw_t中的methods(),每一项需要加.big()来打印
d)获取类方法(+()):元类中的bits->class_rw_t->methods(),每一项需要加.big()来打印
3、以method_array_t为例,结构图如下:
