本文的主要目的是分析 类 & 类的结构,整篇都是围绕一个类
展开的一些探索
类的分析
类的分析主要是分析 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
属性。所以对象都有一个isa
objc_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 _bucketsAndMaybeMask; //8字节
union {
struct {
explicit_atomic _maybeMask; //4字节
#if __LP64__
uint16_t _flags; //2字节
#endif
uint16_t _occupied;//2字节
};
explicit_atomic _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 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属性