接下来,深入探索类的结构
一、isa走位
在对象的初始化
过程中,学习了对象isa
的初始化,并且isa
中的shiftcls
指向了对象所对应的类。
通过以下案例我们可以再次验证这一点,即对象内存结构的前八个字节是对象的isa
,并且指向了p1对象
的类JHSPerson
。见下图:
1.元类
在前面我们已经知道objc_class继承于objc_object
,万物皆对象,也就是说,类也有一个isa指针
,那么类isa指向什么呢?
接着上面,打印JHSPerson类中
的isa指针
所指向的内容,发现也是JHSPerson
!难道类isa
指向自己吗?
不是,我们发现对象isa
所指向的JHSPerson类
的地址为。。。。。,而JHSPerson类
中isa
所指向的JHSPerson
地址为。。。。。,说明这是两个不同的类!见下图:
这里引入一个元类
的概念,元类的定义和创建都是由编译器自动完成
!可以理解为,为类的方法找到一个归属
!其实这里类的isa指向了元类
,这些操作是编译器完成的。
总结
对象isa -> 类,类isa -> 元类。
2.根元类
在上面的案例中,我们通过类对象
的isa
找到了元类对象
,元类
既然也是对象
,那么它的isa
又指向谁呢?
依然是上面的案例,我们打印JhsPerson
元类isa
的指向,结果是NSObject
!?见下图:
验证一下:NSObject类的地址
和JhsPerson元类isa所指向的NSObject
是否相同?见下图:
从打印结果发现,并不相同!那么NSObject类
的isa
又指向什么呢?
从结果中可以发现,NSObject类
的isa
指向NSObject元类
,JhsPerson
元类isa
也指向NSObject元类
。因为NSObject
是所有类的根类
,所以将NSObject元类
称为根元类
。
那么根元类
的isa
又指向哪里呢?见下图:
通过打印根元类
的内存空间,发现根元类
的isa
指向了自己。
总结
元类isa -> 根元类
,NSObject isa -> 根元类
,根元类 isa -> 自己
。
3.isa走位总结
补充:创建一个JhsTeacher类
,继承JhsPerson类
,同样按照上面的分析方式,发现JhsPerson类
的isa
指向的是根元类
,JhsTeacher元类
的isa
也指向根元类
。
通过上面的isa
分析,可以得出isa
的走位图:
二.superclass走位
在进行superclass
走位分析之前,先要确定superclass
指针所在位置,查看objc_class源码
实现:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
....
....
等等......
}
通过上面的源码可以确定,superclass指针
在类的第二个8字节中
1.类superclass
引入一个案例JhsTeacher
继承自JhsPerson
,JhsPerson
继承NSObject
,以我们现有的知识储备,很容理解superclass
的走位,即:JhsTeacher superclass -> JhsPerson
,JhsPerson superclass -> NSObject
,NSObject superclass -> nil
。
2.元类superclass
类的superclass
我们很熟悉,也很好理解,那么元类的superclass
走位是怎样的呢?
同样的思路,首先找到LGTeacher元类
,然后根据元类superclass
开始分析。
从上面的流程可以发现,JhsTeacher元类
的superclass
指向JhsPerson
,JhsPerson
的superclass
指向NSObject
,NSObject
的superclass
指向NSObject根类
。通过验证可以确定,这里的JhsPerson
为元类
,并且其superclass
指向的是NSObject元类
。
总结
JhsTeacher元类 superclass -> JhsPerson元类
,JhsPerson元类 superclass -> NSObject元类
,NSObject元类 superclass -> NSObject类
。
3.superclass总结
根据上面的分析,可以确定以下superclass
走位图:
isa
、superclass
总结,这里使用苹果官方的一走位图,将isa
和superclass
放在一起
三.类结构分析
在进行类的结构分析前,需要明确的是OC层
与c\c++层
的对应关系。见下图:
OC
层,万物皆对象,大部分的类均继承自NSObject
。- 使用
clang
查看源码,定义的别名NSObject
为objc_object
类型。 c\c++
层,类的定义为objc_class
,继承自objc_object
,与OC
层一致,万物皆对象。
1.类的结构初步分析
在libObjc.A.dylib库
中,全局搜索obj_class
,得到其源码实现:
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
....... 等等
}
类中包含四个重要的属性,Class ISA
、Class superclass
、cache_t cache
、class_data_bits_t bits
。
-
isa
和superclass
前面已经做了分析,这两个指针确定了类之间的关系。 -
cache
中存储了运行中的一些缓存信息,比如消息缓存。从objc_class
提供的方法可以看出,在获取一些数据时,优先从缓存中获取,如果没有缓存,则从bits
中获取。这样设计的目的是保证响应速度
。 -
bits
,类中相关的数据信息都存储在了bits
中。
2.cache_t cache
cache_t源码如下:
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
......等等
}
- 结构体空间大小
解读cache_t
源码,提供了两个属性,_bucketsAndMaybeMask
和一个联合体
,其中_bucketsAndMaybeMask
为uintptr_t
泛型,占8个字节
。联合体中包含一个结构体和一个指针,所以联合体也占用8个字节
,所以cache_t
一共占用16字节
的内存空间。
2.class_data_bits_t bits
class_data_bits_t
是一个结构体,objc_class
为其提供了两个重要的方法,data()
和setData()
。
// objc_class - 类
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
// class_data_bits_t - rw
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
在类的实现过程中,会创建一个class_rw_t
,会将MachO文件
中的class_ro_t数据
拷贝一份到class_rw_t
中,并设置到类中。在class_rw_t
中提供了一些方法,可以获取类的属性、协议、分类、方法等。
四.类结构探索
引入一个案例,来探索类的结构,定义了JhsPerson类
,包括了两个属性
,一个成员变量
,一个对象方法
和一个类方法
,见下面代码:
@interface JhsPerson : NSObject{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 0x00007ffffffffff8
JhsPerson *p = [[JhsPerson alloc]init];
JhsTeacher *t = [[JhsTeacher alloc]init];
NSLog(@"%@",p);
}
return 0;
}
设置断点,运行程序,获取JhsPerson
类的地址,并答应类的内存结构。见下图:
很显然0x0000000100008278
为指向元类的isa
,0x0000000100379140
为superclass
,那么如果要探索bits
数据,应该查看那部分地址呢?
从上面的源码结构可以知道,isa
占用8字节
,superclass
占用8字节
,cache
占用16字节
,所以只要类地址平移32字节
即可获取bits
的首地址!
其中类的首地址为0x1000082a0
,那么bits首地址 = 0x1000082a0 + 0x20
。 通过上面的分析,将bits首地址
强制转换为class_data_bits_t *
即可成功获取class_data_bits_t
数据结构。见下图:
此时bits
不等于空,所以类中已经存储了相关的数据信息。继续探索,我们上面已经说明,class_data_bits_t
结构体中提供了data()方法
,用于获取class_rw_t
,class_rw_t
是在类初始化过程中已经被创建了,并且class_rw_t
的相关数据来自MachO文件
中ro
数据!
下面获取class_rw_t
内容:
我们已经探索到了类的class_rw_t
中,在这里我们可以获取相关的地方、属性等内容。
1.探索方法
查看源码可知,在class_rw_t
中提供了const method_array_t methods()方法
,在这里寻找突破口。
至此获取到了method_list_t
,并且里面有count = 5
个方法,只要便利该列表就可以拿到相关方法了。打印输出第一个元素:
很遗憾没有输出结果,为空?查看method_t
的源码实现,内部有一个big结构体
,该结构体包括了方法编号SEL
,方法type encoding
,和方法实现
。
struct method_t {
static const uint32_t smallMethodListFlag = 0x80000000;
method_t(const method_t &other) = delete;
// The representation of a "big" method. This is the traditional
// representation of three pointers storing the selector, types
// and implementation.
struct big {
SEL name;
const char *types;
MethodListIMP imp;
};
.....等等
}
打印输出big数据
,见下图:
缺一张图
成功输出了五个方法!其中name
和hobby
属性会自动生成get\set方法
,但是没有类方法,全是对象方法!!!类+say666()方法在哪里呢?
2.探索属性
和探索方法一样的方式,通过调用class_rw_t
中的properties()
方法,可以获取属性列表。运行结果如下:
成功找到了name
和hobby
两个属性,那么成员变量subject放在哪里呢,name和hobby对应的_成员变量
又放在那呢?``
3.class_ro_t谜底
走读class_rw_t
的源码,并没有发现存储变量的相关属性,也没有获取变量的相关方法,但是在const class_ro_t *ro()方法
获取的class_ro_t
结构体中有一个属性const ivar_list_t * ivars;
,并且是一个常量!说明在编译之初已经确定,运行时也不会修改!
下面从ro()方法
中寻找突破口!见下图:
在class_ro_t
中,包括方法列表、协议列表、变量列表等,成员变量是否存储在这里呢?进去看看:
完美,成功找到了JhsPerson
的三个成员变量,subject
、_name
、_hobby
均已找到!!!
4.类方法 --- 待补充
五.ro rw rwe补充
-
ro
属于clean memory
,在编辑时即确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息; -
rw
的数据空间属于dirty memory
,rw
是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存。 -
rwe
类的额外信息。在rw
中只有10%
的类真正的更改了他们的方法,并不是每一个类都需要插入数据,进行修改的类很少,避免资源的消耗,所以就有了rwe
。