前言
我们编写的Objective-C代码,底层通过C/C++代码实现。
Objective-C -> C/C++ -> 汇编语言 -> 机器语言
那么一个Objective-C类、对象是如何通过C/C++实现、储存的呢?
源码探究
ISA
我们通过以下命令,将.m文件转化为C/C++代码。
1 | clang -rewrite-objc **.m |
可以获取 **.cpp文件。
在7700多行,可以获取如下结构体
1 | struct NSObject_IMPL { |
在objc.h头文件中,我们可以发现如下代码
1 | #if !OBJC_TYPES_DEFINED |
通过注释我们得知:
- 结构体objc_class表示一个Objective-C的类。
- 结构体objc_object表示一个Objective-C Class的实例。
那么objc_class又是什么呢?
在runtime.h文件中,我们可以发现如下代码:
1 | struct objc_class { |
我们可以得知:在Objective-C中,每个对象都是一个结构体,都有一个isa指针,类对象Class也是一个对象。在运行时,可以通过isa指针查找该对象是什么类。
那么isa到底是什么?为什么需要isa呢?
我们可以通过objc4源码获取一些信息
objc4 的源码不能直接编译,需要配置相关环境才能运行。可以在这里下载可调式的源码。
objc 运行时源码的入口在 void _objc_init(void) 函数。
objc-private.h
1 | struct objc_object { |
objc-runtime-new.h
1 | struct objc_class : objc_object { |
通过源码可以得知,objc_class继承于objc_object,所以二者都存在isa成员变量,类型为:isa_t。isa_t是一个union。
我们回到objc-private.h中
1 | union isa_t { |
isa_t包含一个成员变量 cls。
每个objc_object通过自己持有的isa查找到自己所属的类。而对于objc_class来说,可以通过isa找到自己的mate class,即元类。
对于IAS_BITFIELD,定义如下:
1 | //__arm64__ |
- nonpointer
表示 isa_t 的类型,0 表示 raw isa,也就是没有结构体的部分,访问对象的 isa 会直接返回一个指向 cls 的指针,也就是在 iPhone 迁移到 64 位系统之前时 isa 的类型。1 表示当前 isa 不是指针,但是其中也有 cls 的信息,只是其中关于类的指针都是保存在 shiftcls 中。
- has_assoc
对象含有或者曾经含有关联引用(category相关),没有关联引用的可以更快地释放内存(object dealloc相关,其他文章会说到)。
- has_cxx_dtor
表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存(object dealloc相关)。
- shiftcls
见nonpointer
- magic
用于调试器判断当前对象是真的对象还是没有初始化的空间
- weakly_referenced
对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
- deallocating
对象正在释放内存
- has_sidetable_rc
对象的引用计数太大了,存不下
- extra_rc
对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc 的值就为 9。
isa初始化
我们可以通过isa初始化方法,来了解这64位bits作用。
1 | inline void |
当我们初始化一个Objective-C对象,为其分配内存时,调用栈中包含了 initInstanceIsa与initIsa这2个方法。
initInstanceIsa方法,传入的nonpointer为true。所以initIsa方法可以简化为(ARM_ARCH_7K应该是watch指令集,所以我们只看else部分):
1 | inline void |
先将 newisa 的 bits 赋值为常量 ISA_MAGIC_VALUE
1 | #define ISA_MAGIC_VALUE 0x000001a000000001ULL |
里面包括了 magic 和 nonpointer 的值。然后将是否有 C++ 析构函数标示上,最后将位移(shift)后的 cls 存入 shiftcls。
1 | newisa.shiftcls = (uintptr_t)cls >> 3; |
将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址。
最后将 isa = newisa,工作就结束了。
方法、属性、协议
在Objective-C中,对象的方法储存在类中,而非实例对象中。
1 | struct class_rw_t { |
1 | // 用于存储一个 Objective-C 类在编译期就已经确定的属性、方法以及遵循的协议 |
当一个对象的实例方法被调用时,需要先通过持有的isa指针查找相应的类,然后在类的class_data_bits_t 结构体中查找对应方法的实现(每个对象可以通过 cls->data()-> methods 来访问所属类的方法)。同时,每一个 objc_class 也有一个指向自己的父类的指针 super_class 用来查找继承的方法。
而当一个类的类方法被调用时,通过类的isa在元类中获取方法的实现。如上图。
结论
isa用于查找对象(或类对象)所属类(或元类)的信息,比如方法列表、属性、协议等。