OC底层原理06 - 类 & 类结构分析
类 的分析
类的分析主要是分析isa
的走向以及继承
关系
- 首先定义两个类,一个
HLPerson
继承自NSObject
,另一个HLTeacher
继承自HLPerson
@interface HLPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *hl_name;
- (void)sayInstance;
+ (void)sayClass;
@end
@implementation HLPerson
- (void)sayInstance {
}
+ (void)sayClass {
}
@end
@interface HLTeacher : HLPerson
@end
@implementation HLTeacher
@end
- 在
main
中创建一个对象person
,并打上断点
image.png - 通过lldb调试,打印相关信息
image.png
通过上面打印,我们可以看到po 0x00000001000080c0
和po 0x000000010034c0f0
打印出来的信息都是HLPerson
,这两个HLPerson
是一样的吗?还是在内存中有两个HLPerson
呢?
其实这两个HLPerson
并不是同一个类,一个是HLPerson类
,还一个是HLPerson元类
元类的定义
-
实例对象
的isa
是指向类
,类
其实也是一个对象
,称为类对象
,其isa
的位域指向的就是元类
-
元类
是系统定义创建的,其定义
和创建
都是由编译器
完成,类归属于元类 -
元类
是类对象
的类,每个类都有一个独一无二的元类
用来存储类方法
等相关信息 -
元类
本身是没有名称
的,由于与类
相关联
,所以使用了同类名
一样的名称
下面通过lldb
命令来探索元类的归属
,也就是isa的走位
,如下图所示,可以得出一个关系链:
实例对象
--> 类对象
--> 元类
--> 根元类(NSObject)
,NSObject
的isa
指向自身
类在内存中存在几份
image.png 通过对象person
的isa
获取到的类信息地址
与HLPerson.class
获取到的地址是相同
的,这意味一个类在内存中不会存在多份,这个结论是否正确呢?我们来验证一下:
Class class1 = [HLPerson class];
Class class2 = [HLPerson alloc].class;
Class class3 = object_getClass([HLPerson alloc]);
NSLog(@"\n%p \n%p \n%p", class1, class2, class3);
运行打印
image.png
三种获取类对象打印地址相同,所以类在内存中只有一份
isa走位与继承关系图
isa流程图.pngisa走位
-
实例对象
(Instance of Subclass
)的isa
指向类对象
(class
) -
类对象
(class
)的isa
指向元类
(Meta class
) -
元类
(Meta class
)的isa
指向根元类
(Root metal class
) -
根元类
(Root metal class
)的isa
指向它自己
,形成闭环
superclass继承关系
-
实例对象
之间没有继承关系 -
类对象
之间的继承关系
:-
类
(subClass
)继承自父类
(superClass
)
... -
父类
(superClass
)继承自根类
(RootClass
),这里根类
是NSObject
-
根类
继承自nil
,所以根类
即NSObject
可以理解为类的根源
,即无中生有
-
-
元类
之间的继承关系
:-
子类
的元类
(metal SubClass
)继承自父类
的元类
(metal SuperClass
)
... -
父类
的元类
(metal SuperClass
)继承自根元类
(Root metal Class
) -
根元类
(Root metal Class
)继承于根类
(Root class
),此时的根类
是指NSObject
-
举例说明
创建三个实例对象
NSObject *objc = [NSObject alloc];
HLPerson *person = [HLPerson alloc];
HLTeacher *teacher = [HLTeacher alloc];
image.png
- isa 走位链
- teacher的isa走位链:
teacher(子类对象) --> HLTeacher(子类) --> HLTeacher(子元类) --> NSObject(根元类) --> NSObject(根元类,即自己)
- person的isa走位链:
person(父类对象) --> HLPerson(父类) --> HLPerson(父元类) --> NSObject(根元类) --> NSObject(根元类,即自己)
- teacher的isa走位链:
- superclass继承链
- 类的继承关系链:
HLTeacher(子类) --> HLPerson(父类) --> NSObject(根类) --> nil
- 元类的继承关系链:
HLTeacher(子元类) --> HLPerson(父元类) --> NSObject(根元类) --> NSObject(根类) --> nil
- 类的继承关系链:
对象的本质
在上一篇文章OC底层原理05 - isa与类关联的原理中,使用clang
编译过main.m文件,从编译后的c++文件中可以看到如下c++源码
-
NSObject
的底层编译是NSObject_IMPL
结构体-
Class
是isa
指针的类型,是由objc_class
定义的 -
objc_class
是一个结构体
。
-
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
- 在objc4源码中搜索
objc_class
的定义,在objc-runtime-new.h
中找到最新的定义 image.png 通过源码可以看出objc_class
其实就是继承自objc_object
- 在objc4源码中搜索
objc_object
image.png
objc_class 与 objc_object 的关系
- 结构体
objc_class
继承自objc_object
,其中objc_object
也是一个结构体,且有一个属性isa
,所以objc_class
也拥有了属性isa
- mian.cpp底层编译文件中,
NSObject
中的isa
在底层是由Class
定义的,其中Class
的底层编码来自objc_class
,所以NSObject
也拥有了属性isa
-
NSObject
是一个类,用它初始化一个实例对象objc
,objc
也有一个isa
属性,该isa
是由NSObject
从objc_class
继承过来的,而objc_class
的isa
继承自objc_object
。所以对象都有一个isa
,isa
表示指向,来自于当前的objc_object
objc_object 与 对象的关系
- 所有的
对象
是继承NSObject(OC)
,但其真正到底层的是objc_object(C/C++)
的结构体类型 - 所有的
对象
都是以objc_object
为模板继承过来的
【总结】objc_object
与对象
是继承
关系
总结
- 所有的
对象
、类
、元类
都有isa
属性 - 所有的
对象
都是由objc_object
继承来的 - 简单来说即
万物皆对象
,万物皆来源于objc_object
,有以下两点结论:- 所有以
objc_object
为模板创建的对象
,都有isa
属性 - 所有以
objc_class
为模板创建的类
,都有isa
属性
- 所有以
- 在结构层面可以通俗的理解为
上层OC
与底层
的对接:-
下层
是通过结构体
定义的模板
,例如objc_class
、objc_object
-
上层
是通过底层的模板创建
的一些类型,例如HLPerson
-
objc_class
、objc_object
、isa
、object
、NSObject
等的整体的关系,如下图所示
类结构的分析
探索类信息
的结构,事先我们并不清楚类的结构是什么样的,但是我们可以获取类的首地址
,然后通过地址平移
去获取里面所有的值
前面我们已经在objc4找到objc_class
源码
struct objc_class : objc_object {
// Class ISA; //8字节
Class superclass; //Class 类型 8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
//....方法部分省略,未贴出
}
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
-
isa
属性:继承自objc_object
的isa
,是一个地址指针,占8
字节 -
superclass
属性:Class
类型,Class
是由objc_object
定义的,也是一个指针,占8
字节 -
cache
属性:cache_t
是一个结构体
类型,结构体
的内存大小
需要根据内部的属性
来确定 -
bits
属性:首地址
平移上面3个属性的内存大小总和
,就可以获取到bits
计算 cache 类的内存大小
进入cache_t
的定义(只贴出了结构体中非static修饰的属性,主要是因为static类型的属性不存在结构体的内存中),有如下几个属性
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets; // 一个结构体指针类型,占8字节
explicit_atomic<mask_t> _mask; // mask_t 类型,mask_t 即 unsigned int,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
explicit_atomic<uintptr_t> _maskAndBuckets; // 是指针,占8字节
mask_t _mask_unused; // mask_t 类型,mask_t 即 uint32_t,占4字节
#if __LP64__
uint16_t _flags; // uint16_t类型,uint16_t 即 unsigned short,占 2个字节
#endif
uint16_t _occupied; // uint16_t类型,uint16_t 即 unsigned short,占 2个字节
};
所以最后计算出cache
类的内存大小为8 + 4 + 2 + 2 = 16
字节
获取bits
objc_class
中前三个属性的大小为:8 + 8 + 16 = 32
字节,所以想获取bits
中的信息可以通过首地址偏移32字节
获得,以下是通过lldb命令调试的过程
- 其中的
data()
获取数据,是由objc_class
提供的方法 image.png
获取属性列表
通过查看class_rw_t
定义的源码发现,结构体中有提供相应的方法去获取属性列表
、方法列表
等,如下所示
-
p $3.properties()
命令中的properties
方法是由class_rw_t
提供的,方法中返回的实际类型为property_array_t
- 由于
list
的类型是property_list_t
,是一个指针,所以通过p *$5
获取内存中的信息,证明bits
中存储了property_list
,即属性列表 -
p $11.get(1)
,想要获取HLPerson
中的成员变量bobby
,打印报错,提示数组越界了,说明property_list
中只有 一个属性hl_name
成员变量的存储
成员变量
:在{ }
中所声明
的变量都是成员变量
(实例变量
是一种特殊的成员变量
)
实例变量
:成员变量
的一种,由类声明
的对象
属性
:@property
修饰,编译器会自动生成带下划线的成员变量
以及setter
和getter
方法
从上面可得看出property_list
中只有属性
,没有成员变量
,在class_rw_t
结构体中有个ro()
可以获取成员变量
【总结】
- 通过
@property
定义的属性
,存储在bits属性中,通过bits --> data() -->properties() --> list
获取属性列表,其中只包含属性
- 通过
{}
定义的成员变量
,也会存储在bits属性中,通过bits --> data() -->ro() --> ivars
获取成员变量列表,除了包括成员变量,还包括属性生成的带下划线的成员变量
获取方法列表
通过lldb调试来查找方法列表 image.png- 通过
p $3.methods()
获得具体的方法列表的list
,其中methods
也是class_rw_t
提供的方法 - 通过打印的
count = 4
可知,存储了4
个方法,可以通过p $11.get(i)
内存偏移的方式获取单个方法,i
的范围是0-3
类方法的存储
在methods list
中并没有找到类方法
, 那类方法
存储在哪里?下面我们来分析下:
前面有分析元类
,元类
是用来存储类的相关信息
的,我们大胆猜测一下:类方法
存储在元类
的bits
中呢?通过lldb命令来验证我们的猜测:
【总结】
-
类
的实例方法
存储在类的bits
属性中,通过bits --> methods() --> list
获取实例方法列表; -
类
的类方法
存储在元类的bits
属性中,通过元类bits --> methods() --> list
获取类方法列表。
类的结构功能
名称 | 类型 | 功能 |
---|---|---|
isa | 指针 | 指向元类 |
superclass | 指针 | 指向当前类的父类 |
cache | 结构体 | 用于缓存方法的,用于加速方法的调用 |
bits | 结构体 | 存储类的方法、属性、协议等信息的地方 |