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文件:
可以发现:
@interface MYPerson: NSObject
,MYPerson继承NSObject 底层体现在 typedef struct objc_object MYPerson;
于是乎,对象的本质就是结构体。
查看NSObject_IMPL
:
struct NSObject_IMPL {
Class isa;
};
显然,MYPerson_IMPL
结构体中NSObject_IVARS
这个成员变量就是isa
。
实际上:
struct objc_class *
类型,NSObject底层是struct objc_object
结构体,id底层是struct objc_object *
类型struct objc_object {
Class _Nonnull isa __attribute__((deprecated));
};
也就是说NSObject底层实现的结构体里只有一个成员变量isa,又因为Class底层是struct objc_class *类型,所以 NSObject的本质是objc_class。
每个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; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
};
};
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 有两种初始化方式:
先看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声明的类的成员变量、类的属性、类的协议。
即id类型(我们平时用的所有对象都是id类型的,在runtime中,id 就是 objc_object结构体)
objc_object结构体主要包含:
isa_t
:共用体。关于isa操作相关的一些方法
:通过objc_object结构体,来获取isa所指向的类对象,或者通过类对象的isa指针获取它的元类对象一些遍历的放大。弱引用相关
:标记一个对象是否曾经有过弱引用指针。关联对象相关方法
:我们为对象设置了关联属性,关于关联属性的一些相关方法也体现在objc_object结构体中。内存管理相关的方法实现
:MRC下经常用到的runtain,release等方法实现,以上均封装在objc_object结构体中OC中的Class
代表一个类,他在runtime中对应objc_class
的数据结构(结构体)。
Class
这样一个类也是一个对象,称为类对象,因为它继承自objc_object
。
objc_class
包含:
当我们进行方法调用时,调用一个实例的实例方法,实际上是通过isa指针,到它的类对象中,去进行方法查找。
如果我们调用的是类方法,那么是通过类对象的isa指针,到它的元类对象中去查找。
cache_t
是用于快速查找方法执行函数的一个结构(当我们调用一个方法时,如果有缓存,我们就不需要去方法列表中遍历了,可以提高方法调用速度)。
注意,cache_t
是可增量扩展的哈希表结构(当结构存储量增大的过程中, cache_t会增量扩大它的内存空间来支持更多的缓存,用哈希表实现这个数据结构,是为了提高查找效率)。
cache_t数据结构是计算机局部性原理的最佳应用(计算机局部性原理:在一般调用方法时,有几个方法调用频次较高,把他们放到方法缓存中,下次的命中率就会更高)。
cache_t
可以理解为是数组实现的。
每个对象都是bucket_t
这样的一个结构体, bucket_t
有两个主要成员变量:key和IMP。
假如现在有个key,可以通过哈希查找算法来定位当前key所对应的bucket_t位于数组当中哪个位置,然后从这个位置中提取出bucket_t中的IMP。
这个结构是objc_class
中的成员结构,class_data_bits_t
主要是对class_rw_t
的封装。
class_rw_t
代表了类相关的读写信息,例如给类添加的分类的一些方法,属性以及协议等,同时它也对class_ro_t的封装,我们可以随时创建分类,为类增加属性或者方法。rw是readWrite的简写,ro是readOnly的意思,创建类时,添加的成员变量或方法列表在之后就没办法修改了
class_ro_t
代表了类相关的只读信息。
class_rw_t
包含:
class_ro_t
这三个数据结构是个二维数组
假如我们三个分类A、B、C,编译顺序A->B->C。这时会逆序遍历并打包成分类数组,分类C中的所有方法都在第一列竖列表中,存在二维数组的第1项。分类B中的所有方法都在第二列竖列表中,存在二维数组的第2项。分类A中的所有方法都在第三列竖列表中,存在二维数组的第3项。
除类名以外,其他成员都是一维数组
在方法列表当中存储的内容,一般是分类中添加的方法
method_t
实际上是对方法的抽象说明,也是对函数四要素(名称、返回值、参数、函数体)的封装,函数四要素决定了函数的唯一性。
method_t是个结构体,主要有三个数据类型:
isa走位图:
NSObject的isa走位图:
上图刚好可以说明下面的问题。
类之间的继承关系:
元类也存在继承,元类之间的继承关系如下:
注意:实例对象之间没有继承关系,类之间有继承关系
系统分配了16个字节给NSObject对象(通过malloc_size获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下通过class_getInstanceSize获得)