类与对象的定义
OC中的类是一个指向objc_class的结构体指针,结构体如下:
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
};
OC中对象的定义是这样:
typedef struct objc_object {
Class isa;
} *id;
每个对象都有一个类,在Objective-C中,对象的类是isa指针决定的,即 isa 指针指向对象所属的类。
OC对象在发送消息时,运行时库会追寻着对象的isa指针得到对象所属的类。这个类包含了能应用于这个类的所有实例方法以及指向父类的指针,以便可以找到父类的实例方法。运行时库检查这个类和其父类的方法列表,找到与消息对应的方法。 编译器会将消息转换为消息函数objc_msgSend进行调用。
元类
实际上,OC的类其实也是一个对象,一个对象就要有一个它属于的类,意味着类也要有一个isa指针,指向其所属的类。那么类的类是什么?就是我们所说的元类(MetaClass),所以,元类就是类的所属类
。
对象的本质
我们创建一个MYPerson类,其拥有两个属性:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface MYPerson : NSObject {
int _age;
int _height;
}
然后我们在终端cd进该文件目录,使用命令clang -rewrite-objc main.m -o main.cpp
将其转换为cpp文件:
可以发现:
- OC中的MYPerson类底层是struct MYPerson_IMPL结构体
- OC中
@interface MYPerson: NSObject
,MYPerson继承NSObject 底层体现在typedef struct objc_object MYPerson;
于是乎,对象的本质就是结构体。
查看NSObject_IMPL
:
struct NSObject_IMPL {
Class isa;
};
显然,MYPerson_IMPL
结构体中NSObject_IVARS
这个成员变量就是isa
。
实际上:
- 每个类的底层都会有一个Class类的isa指针
- Class 底层是
struct objc_class *
类型,NSObject底层是struct objc_object
结构体,id底层是struct objc_object *
类型 - struct objc_object的实现是:
struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
也就是说NSObject底层实现的结构体里只有一个成员变量isa,又因为Class底层是struct objc_class *类型,所以 NSObject的本质是objc_class。
isa分析
每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。
什么是共用体?
我们看demo:
定义结构体Test1和共用体Test2
struct Test1 {
char *name;
int age;
double height;
};
union Test2 {
char *name;
int age;
double height;
};
struct Test1 test1;
test1.name = "tom";
test1.age = 20;
union Test2 test2;
test2.name = "tom";
test2.age = 20;
NSLog(@"test1:%ld", sizeof(test1));
NSLog(@"test2:%ld", sizeof(test2));
输出结果:
简单理解:
结构体会给所有的成员变量按照内存对齐规则
分配内存,所以分配了24字节的内存。
联合体里的变量内存是公用的
,所以只需要开辟一个最大变量需要的内存就够了
,最大的是double类型的所以分配了8个字节。
内存对齐:
1.对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
2.结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
下面,我们看看isa_t的定义:
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
uintptr_t has_assoc : 1;//->是否包含关联对象
uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
uintptr_t magic : 6;//->固定值,用于判断是否完成初始化
uintptr_t weakly_referenced : 1;//->对象是否被弱引用
uintptr_t deallocating : 1;//->对象是否正在销毁
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
};
};
isa的初始化过程
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
isa = newisa;
}
方法内创建了一个 isa_t
类型的 newisa
实例, 做了赋值操作后,返回了newisa。
下面看一下 isa_t
的底层实现。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
isa_t
是一个联合体, 有两个成员变量:bits和cls。由于联合体的特性,其中各变量是互斥的, 这样可以使内存使用更为精细灵活。于是,isa_t 有两种初始化方式:
- bits 被初始化, cls 没有值或者值被覆盖
- cls 被初始化, bits 没有值或者值被覆盖
runtime底层结构
先看runtime底层的数据结构:
首先从objc_class结构体开始,objc_class继承于objc_object。
objc_object当中有一个成员变量叫isa_t
,那么这个isa_t指针就指向一个objc_class类型的类对象。
objc_class主要包含三个成员变量,superClass(指向当前类的父类)、cache_t(用来提供消息传递过程中的缓存方法查找)、class_data_bits_t(类的一些基本信息:类所定义/通过分类所添加的成员变量,属性,方法列表都在这个数据结构中)
superClass
:实际是class类型的,它指向objc_class类型的这样一个指针cache_t
:实际上是装满了bucket_t数据结构的Hash表,维护的也就是这个hash表class_data_bits_t
:实际上是对class_rw_t的数据结构的封装class_rw_t中包含了(class_ro_t类相关的只读信息、protocols类分类中的协议、properties类分类中的属性、methods类分类中的方法)
class_ro_t
包含了name类名、methodList类的方法列表–method_t、ivars声明的类的成员变量、类的属性、类的协议。
objc_object
即id类型(我们平时用的所有对象都是id类型的,在runtime中,id 就是 objc_object结构体)
objc_object结构体主要包含:
isa_t
:共用体。关于isa操作相关的一些方法
:通过objc_object结构体,来获取isa所指向的类对象,或者通过类对象的isa指针获取它的元类对象一些遍历的放大。弱引用相关
:标记一个对象是否曾经有过弱引用指针。关联对象相关方法
:我们为对象设置了关联属性,关于关联属性的一些相关方法也体现在objc_object结构体中。内存管理相关的方法实现
:MRC下经常用到的runtain,release等方法实现,以上均封装在objc_object结构体中
objc_class
OC中的Class
代表一个类,他在runtime中对应objc_class
的数据结构(结构体)。
Class
这样一个类也是一个对象,称为类对象,因为它继承自objc_object
。
objc_class
包含:
- objc_class拥有一个superClass指针,指向class类型,(如果说Class是一个类对应的话,superClass指针指向的就是它的父类对象,就是我们平时说的类与父类,实际上是通过objc_class中superClass成员变量来定义的)。
- cache_t cache成员变量: 方法缓存结构,消息传递时会使用这样的数据结构。
- class_data_bits_t bits数据结构: 关于类所定义的变量,属性,方法都在bits这样一个成员结构中。
isa指针
isa指针分两种类型:
- 指针型isa:64位的0或者1的整体内容代表所指向的Class的地址,也就是可以通过isa的内容来获得类对象的地址。
- 非指针型isa:isa的值的部分代表Class的地址,之所以这样是因为我们在寻址过程中,只有三四十位数就可以保证我们寻找到所有Class地址了,多出来的位可以用来存储其他相关内容,来达到节省内存的目的。
isa指向
- 关于对象,它指向类对象
例如我们拥有一个实例,实例就是OC中对应的id类型,在Runtime中就是objc_object,里面有个isa指针,会指向它对应的Class。 - 关于类对象,指向元类对象
class因为集成objc_object,所以里面也有isa指针,指向其元类对象。
当我们进行方法调用时,调用一个实例的实例方法,实际上是通过isa指针,到它的类对象中,去进行方法查找。
如果我们调用的是类方法,那么是通过类对象的isa指针,到它的元类对象中去查找。
cache_t
cache_t
是用于快速查找方法执行函数的一个结构(当我们调用一个方法时,如果有缓存,我们就不需要去方法列表中遍历了,可以提高方法调用速度)。
注意,cache_t
是可增量扩展的哈希表结构(当结构存储量增大的过程中, cache_t会增量扩大它的内存空间来支持更多的缓存,用哈希表实现这个数据结构,是为了提高查找效率)。
cache_t数据结构是计算机局部性原理的最佳应用(计算机局部性原理:在一般调用方法时,有几个方法调用频次较高,把他们放到方法缓存中,下次的命中率就会更高)。
cache_t
可以理解为是数组实现的。
每个对象都是bucket_t
这样的一个结构体, bucket_t
有两个主要成员变量:key和IMP。
- key对应OC中的selector,在调用方法时是个选择器SEL。
- IMP理解为无类型的函数指针,可以通过方法选择器的名称key来寻找这个方法的具体实现IMP。
假如现在有个key,可以通过哈希查找算法来定位当前key所对应的bucket_t位于数组当中哪个位置,然后从这个位置中提取出bucket_t中的IMP。
class_data_bits_t
这个结构是objc_class
中的成员结构,class_data_bits_t
主要是对class_rw_t
的封装。
class_rw_t
class_rw_t
代表了类相关的读写信息,例如给类添加的分类的一些方法,属性以及协议等,同时它也对class_ro_t的封装,我们可以随时创建分类,为类增加属性或者方法。rw是readWrite的简写,ro是readOnly的意思,创建类时,添加的成员变量或方法列表在之后就没办法修改了
class_ro_t
代表了类相关的只读信息。
class_rw_t
包含:
class_ro_t
- protocols类分类中的协议
- properties类分类中的属性
- methods类分类中的方法
这三个数据结构是个二维数组
假如我们三个分类A、B、C,编译顺序A->B->C。这时会逆序遍历并打包成分类数组,分类C中的所有方法都在第一列竖列表中,存在二维数组的第1项。分类B中的所有方法都在第二列竖列表中,存在二维数组的第2项。分类A中的所有方法都在第三列竖列表中,存在二维数组的第3项。
class_ro_t
class_ro_t
包含:
- name:类名
- ivars:声明的类的成员变量
- 类的属性
- 类的协议
- 类的方法列表
除类名以外,其他成员都是一维数组
在方法列表当中存储的内容,一般是分类中添加的方法
method_t
method_t
实际上是对方法的抽象说明,也是对函数四要素(名称、返回值、参数、函数体)的封装,函数四要素决定了函数的唯一性。
method_t是个结构体,主要有三个数据类型:
- name函数名称
- types 函数返回值和参数的集合
- imp 无类型的函数指针,对应着函数体
继承链
isa走位图:
NSObject的isa走位图:
上图刚好可以说明下面的问题。
isa走位
- 实例对象(Instance of Subclass)的isa指向类(class)
- 类对象(class)的isa指向元类(Meta class)
- 元类(Meta class)的isa指向根元类(Root metal class)
- 根元类(Root metal class)的isa指向它自己本身,形成闭环,这里的根元类就是NSObject
superclass走位
类之间的继承关系:
- 类(subClass)继承自父类(superClass)
- 父类(superClass)继承自根类(RootClass),此时的根类是指NSObject
- 根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有
元类也存在继承,元类之间的继承关系如下:
- 子类的元类(metal SubClass)继承自父类的元类(metal SuperClass)
- 父类的元类(metal SuperClass)继承自根元类(Root metal Class)
- 根元类(Root metal Class)继承于根类(Root class),此时的根类是指NSObject
注意:实例对象之间没有继承关系,类之间有继承关系
完整的isa走位图:
继承链:
经典老图:
Q&A
一个NSObject对象占用多少内存?
系统分配了16个字节给NSObject对象(通过malloc_size获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下通过class_getInstanceSize获得)
对象的isa指针指向哪里?
- instance对象的isa指针指向class。当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
- class对象的isa指向meta-class。当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用。类对象的superClass指针,当有继承的时候,调用用父类的对象方法或者类方法时。通过isa指针找到class,然后通过superclass找到继承类的class,.找到对象方法调用
- meta-class对象的isa指针指向基类的meta-class对象
OC的类信息存放在哪里?
- 对象方法,属性,成员变量,协议信息存放在class对象中
- 类方法存放在meta-class对象中
- 成员变量的具体值存放在instance对象中