本文的主要目的是分析 类 & 类的结构,整篇都是围绕一个类展开的一些探索
类的分析
类的分析主要是分析 isa的走向 以及 继承关系
准备工作
定义两个类
- 继承自
NSObject的类HTPerson
@interface HTPerson : NSObject
{
NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayHello;
+ (void)sayBye;
@end
@implementation HTPerson
- (void)sayHello
{}
+ (void)sayBye
{}
@end
- 继承自
HTPerson的类HTTeacher
@interface HTTeacher : HTPerson
@end
@implementation HTTeacher
@end
- 在
main中分别用两个定义两个对象:person & teacher
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson *person = [[HTPerson alloc] init];
HTTeacher *teacher = [[HTTeacher alloc] init];
NSLog(@"person:%@======teacher:%@", person, teacher);
}
return 0;
}
元类
首先,我们先通过一个案例的lldb调试先引入元类
- 在main中
HTTeacher部分加一个断点,运行程序 - 开启lldb调试,调试的过程如下图所示


根据调试过程,我们产生了一个疑问:为什么图中的p/x 0x011d80010000828d & 0x00007ffffffffff8ULL 与 p/x 0x0000000100008260 & 0x00007ffffffffff8ULL 中的类信息打印出来都是HTPerson?
-
0x011d80010000828d是person对象的isa指针地址,其&后得到的结果是创建person的类HTPerson -
0x0000000100008260是isa中获取的类信息所指的类的isa的指针地址,即HTPerson类的类的isa指针地址,在Apple中,我们简称HTPerson类的类为元类 - 所以,两个打印都是
HTPerson的根本原因就是因为元类导致的
元类的说明
下面来解释什么是元类,主要有以下几点说明:
- 我们都知道
对象的isa 是指向类,类其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类 -
元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类 -
元类是类对象的类,每个类都有一个独一无二的元类用来存储类方法的相关信息。 -
元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称
下面通过lldb命令来探索元类的走向,也就是isa的走位,如下图所示,可以得出一个关系链:对象 --> 类 --> 元类 --> NSObject的元类, NSObject的元类 指向自身

总结
从图中可以看出
-
对象的isa指向类(也可称为类对象) -
类的isa指向元类 -
元类的isa指向根元类,即NSObject的元类 -
根元类的isa指向 它自己
著名的 isa走位 & 继承关系 图
根据上面的探索以及各种验证,对象、类、元类、根元类的关系如下图所示

isa走位
isa的走向有以下几点说明:
-
实例对象(Instance of Subclass)的isa指向类(class) -
类对象(class)isa指向元类(Meta class) -
元类(Meta class)的isa指向根元类(Root meta class) -
根元类(Root meta class)的isa指向它自己本身,形成闭环,这里的根元类就是NSObject的元类
superclass走位
superclass(即继承关系)的走向也有以下几点说明:
- 类之间的继承关系:
-
类(subClass)继承自父类(superClass) -
父类(superClass)继承自根类(RootClass),此时的根类是指NSObject -
根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有
-
- 元类也存在继承,元类之间的继承关系如下:
-
子类的元类(meta SubClass)继承自父类的元类(meta SuperClass) -
父类的元类(meta SuperClass)继承自根元类(Root meta Class) -
根元类(Root meta Class)继承于根类(Root class),此时的根类是指NSObject
-
- 【注意】
实例对象之间没有继承关系,类之间有继承关系
objc_class & objc_object
isa走位我们理清楚了,又来了一个新的问题:为什么 对象 和 类都有isa属性呢?这里就不得不提到两个结构体类型:objc_class & objc_object
- 在objc4源码中搜索
objc_class的定义,源码中对其的定义有两个版本-
旧版:位于runtime.h中,在OBJC2中已废弃

-
新版:位于objc-runtime-new.h中,我们后面的类的结构分析也是基于新版来分析的。

-
从新版的定义中,可以看到 objc_class 结构体类型是继承自 objc_object的
- 在objc4源码中搜索
objc_object的定义,发现也是有两个版本-
一个是位于
objc.h中,从编译的main.cpp中可以看出,使用的是这个版本的objc_object

-
另外一个位于
objc-privat.h

-
以下是编译后的main.cpp中的objc_object 和NSObject_IMPL的定义
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
【问题】objc_class 与 objc_object 有什么关系?
通过上述的源码查找以及main.cpp中底层编译源码,有以下几点说明:
objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性mian.cpp底层编译文件中,
objc_object中的isa在底层是由Class定义的,其中Class的底层编码是struct objc_class *的别名(即结构体指针),所以NSObject也拥有了isa属性NSObject是一个类,用它初始化一个实例对象objc,objc满足objc_object的特性(即有isa属性),主要是因为isa是由NSObject从objc_class继承过来的,而objc_class继承自objc_object,objc_object有isa属性。所以对象都有一个isaobjc_object(结构体)是当前的根对象,所有的对象都有这样一个特性objc_object,即拥有isa属性
总结
- 所有的
对象、类、元类都有isa属性 - 所有的
对象都是由objc_object继承来的 - 简单概括就是
万物皆对象,万物皆来源于objc_object,有以下两点结论:- 所有以
objc_object为模板,创建的对象,都有isa属性 - 所有以
objc_class为模板,创建的类,都有isa属性
- 所有以
类结构分析
接下来主要是分析类信息中存储了哪些内容
内存偏移
在分析类结构之前,需要先了解内存偏移,因为类信息中访问时,需要使用内存偏移
普通指针
// 普通指针
int a = 10; // 变量
int b = 11;
int c = 12;
int d = 13;
NSLog(@"%d -- %p", a, &a);
NSLog(@"%d -- %p", b, &b);
NSLog(@"%d -- %p", c, &c);
NSLog(@"%d -- %p", d, &d);
打印结果如下图所示:

- 变量a、b、c、d存储在
栈区,是连续的内存地址,栈区直接存储的是值 - a、b、c、d的地址之间都相差
4字节,这取决于他们存储的类型,Int类型占4字节 - 栈区的地址 比 堆区的地址 大
- 栈是从
高地址->低地址,向下延伸,由系统自动管理,是一片连续的内存空间 - 堆是从
低地址->高地址,向上延伸,由程序员管理,堆空间结构类似于链表,是不连续的
对象指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 对象指针
HTPerson *p1 = [[HTPerson alloc] init];
HTPerson *p2 = [[HTPerson alloc] init];
NSLog(@"%@ -- %p", p1, &p1);
NSLog(@"%@ -- %p", p2, &p2);
NSLog(@"end");
}
return 0;
}
打印结果如下图所示:

-
alloc开辟的内存在堆区,局部变量存储在栈区 - p1、p2存储在栈区,p1、p2内存中存储的是
[HTPerson alloc]的堆区地址
数组指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p ", &c, &c[0], &c[1]);
NSLog(@"%p - %p - %p ", d, d+1, d+2);
for (int i = 0; i<4; i++) {
//(d+i) 取地址里面的值
int value = *(d+i);
NSLog(@"value = %d",value);
}
NSLog(@"end");
}
return 0;
}
打印结果如下图所示:

-
&c和&c[0]都是取首地址,即数组名等于首地址 -
&c与&c[1]相差4字节,地址之间相差的字节数,主要取决于存储的数据类型,即步长 - 可以通过
首地址+偏移量取出数组中的其他元素,其中偏移量等于步长 * 数组的下标
类结构组成
在前面的内容,我们可以得到类的首地址,但是我们还不清楚类的结构是什么,里面都存储了什么东东
打开objc-818.2源码,在objc-runtime-new.h中找到 objc_class的定义,如下
typedef struct objc_class *Class;
struct objc_class : objc_object {
...
// Class ISA; // 8字节
Class superclass; //8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// ...方法部分省略,未贴出
}
objc_class继承自objc_object,因此拥有isa属性,从源码我们可以看出,objc_class包含下列四个变量:
-
isa属性:结构体指针,继承自objc_object的isa,占8字节 -
superclass属性:结构体指针,指向父类的地址,占8字节 -
cache属性:是一个cache_t结构体类型,取决于cache_t内部结构体成员的大小,占16字节 -
bits属性:是一个class_data_bits_t结构体类型,占8字节
我们可以通过lldb+sizeof()查看cache_t和class_data_bits_t的内存大小,如下图所示:

【验证】objc_class的内存大小
定义两个类HTPerson和HTTeacher,通过断点来查看类和元类的地址,寻找规律

- 从上图可以发现,这几个类地址之间相差
0x28(40字节),正好和objc_class结构体中成员变量所占的内存相同,都是40字节
探究类里面的各个成员变量,成员变量的内存地址如下
-
isa的内存地址是类首地址 -
superclass的内存地址是首地址+0x8 -
cache的内存地址是首地址+0x10 -
bits的内存地址是首地址+0x20
isa属性
objc_class继承自objc_object,因此拥有isa属性,isa是结构体指针,占8字节
-
类对象(class)isa指向元类(Meta class) -
元类(Meta class)的isa指向根元类(Root meta class) -
根元类(Root meta class)的isa指向它自己本身,形成闭环,这里的根元类就是NSObject的元类
superclass属性
Class superclass; //8字节也是结构体指针,占8字节
- 类之间的继承关系:
-
类(subClass)继承自父类(superClass) -
父类(superClass)继承自根类(RootClass),此时的根类是指NSObject -
根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有
-
- 元类也存在继承,元类之间的继承关系如下:
-
子类的元类(meta SubClass)继承自父类的元类(meta SuperClass) -
父类的元类(meta SuperClass)继承自根元类(Root meta Class) -
根元类(Root meta Class)继承于根类(Root class),此时的根类是指NSObject
-
- 【注意】
实例对象之间没有继承关系,类之间有继承关系
cache属性
cache_t结构体大小
首先,我们来探究cache_t结构体,源码定义如下
typedef unsigned long uintptr_t;
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4字节
#if __LP64__
uint16_t _flags; //2字节
#endif
uint16_t _occupied;//2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8字节
};
//...
//下面是一些static属性和方法,并不影响结构体的内存大小,主要是因为static类型的属性 不存在结构体的内存中
}
cache_t是结构体类型,有两个成员变量:_bucketsAndMaybeMask和一个联合体
-
_bucketsAndMaybeMask是uintptr_t类型,占8字节 - 联合体里面有两个成员变量:
结构体和_originalPreoptCache,联合体由最大的成员变量的大小决定-
_originalPreoptCache是preopt_cache_t *结构体指针,占8字节 - 结构体中有
_maybeMask、_flags、_occupied三个成员变量。-
_maybeMask的大小取决于mask_t即uint32_t,占4字节 -
_flags是uint16_t类型,占2字节 -
_occupied是uint16_t类型,占2字节
-
-
所以cache_t的大小等于 8+8或者8+4+2+2,即16字节
?我们看几个重要的函数
fastInstanceSize函数
size_t fastInstanceSize(size_t extra) const {}记录实例对象需要分配的内存大小(16字节对齐),我们在iOS底层原理02:alloc & init & new 源码分析已经分析过了
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
// Gcc的内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
// 删除由setFastInstanceSize添加的FAST_CACHE_ALLOC_DELTA16 8个字节
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
insert函数
void insert(SEL sel, IMP imp, id receiver);函数用来插入sel和imp,方法缓存,我们会在后面的篇章进行分析
bits属性
bits属性是class_data_bits_t结构体类型,有一个成员变量bits,占8字节,结构如下
struct class_data_bits_t {
friend objc_class; // 友元:objc_class可以调用 class_data_bits_t中的私有属性和方法
// Values are the FAST_ flags above.
uintptr_t bits; // 8字节,类似于isa,通过位域来存储数据
// ...
public:
// 通过data()获取数据
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// ...方法较多
}
- 继续查看
class_rw_t结构体
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
// ...
const class_ro_t *ro() const {...}
void set_ro(const class_ro_t *ro) {...}
const method_array_t methods() const {...}
const property_array_t properties() const {...}
const protocol_array_t protocols() const {...}
}

现在我们对objc_class的结构有了初步的认识,在后面的篇章中继续深入分析bits和cache两个属性
类结构分析传送门:
- iOS底层原理08:类结构分析——bits属性
- iOS底层原理09:类结构分析——cache属性










