运行时源码版本 objc4-750.1
OC中的id(实例对象)以及NSObject(类)到底是什么
//在objc-private.h文件中
typedef struct objc_class *Class;
typedef struct objc_object *id;
//在objc-private.h文件中
struct objc_object {
private:
isa_t isa; //共用体类型, 具体的定义在上面
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
*** 省略 ***
};
//在objc-runtime-new.h文件中
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
*** 省略 ***
};
通过代码可以看出平时我们的实例对象, 类都是都是C语言结构体, 并且objc_class是继承与objc_object. 也就是说其实类也某一个 '类' 的实例对象, 这个特殊的 '类' 我们称之为 元类
元类(meta class)
通过上面的代码我们可以确定Objective-C 中类也是一个对象, 我们把类对象所属的类型称之为元类(meta class), 元类(meta class)也是一个叫做根元类(root meta class)的实例,那么有了元类的好处是啥呢?
我们知道实例对象的方法是定义在它所属的类当中的,那么类方法自然是定义在元类当中的. 通过这个元类可以保证无论是类还是对象都能通过相同的机制查找方法的实现。
从图中可以看出来对象的isa指向它所属的类,元类的isa都指向根元类(root meta class), 而根元类的isa则指向了自己, 这样就形成了一个闭环的结构.
当实例方法被调用时,它可以通过持有的 isa 来查找对应的类,然后在这里的 class_data_bits_t 结构体中查找对应方法的实现。同时,每一个 objc_class 也有一个指向自己的父类的指针 super_class 用来查找继承的方法。
class_data_bits_t 中存有 Class 的对应方法,具体如何存储及查找以后会另做分析
isa_t isa(共用体类型的isa)
从上面objc_object的定义以及objc_class是继承与objc_object的关系我们可以得到如下图的关系
所有继承自 NSObject 的类实例化后的对象都会包含一个类型为 isa_t 的共用体.
那么 isa 到底是什么呢?其实在 ARM 64 之前,isa 直接保存了类对象或者元类对象的地址,而之后使用了位域结构存储了更多信息。
//64位之后的isa定义
//在objc-private.h文件中
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
看一下ISA_BITFIELD定义
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
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; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
上面列出了ISA_BITFIELD在arm64架构和x86_64架构下的定义, iOS 应用为 arm64 架构环境。由于我的测试代码是基于 macOS 的所以我们下面分析的时候看的是 x86_64 架构下的定义, 这两种结构的实现和位数可能有些差别,但这些字段都是存在的.所以不会影响我们对isa的理解.
// __x86_64__架构
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
先看一下每一个参数的含义
变量名 | 所占位数 | 含义 |
---|---|---|
nonpointer | 1 | 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数 |
has_assoc | 1 | 表示该对象是否包含 associated object,如果没有,则析构时会更快 |
has_cxx_dtor | 1 | 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快 |
shiftcls | 44 | 类的指针 |
magic | 6 | 固定值,用于在调试时分辨对象是否未完成初始化 |
weakly_referenced | 1 | 表示该对象是否有过 weak 对象,如果没有,则析构时更快 |
deallocating | 1 | 表示该对象是否正在析构 |
has_sidetable_rc | 1 | 表示该对象的引用计数值是否过大无法存储在 isa 指针 |
extra_rc | 8 | 存储引用计数值减一后的结果 |
isa是一个共用体,共用体的所有成员占用同一段内存.而isa总共占用的内存是64位, 表中的数字代表每个变量占用的位数.
cache_t cache(方法缓存)
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
通过源码我们可以知道cache_t是有一个bucket_t类型的结构体和两个uint32_t类型的变量组成。
- bucket_t是一个散列表,用来存储类中的方法的链表,缓存曾经调用过的方法,可以调高方法的查找速度
- _mask是表示分配用来缓存buckets的总数
- _occupied是表明当前实际占用缓存buckets的个数
其实cache主要的作用就是为了提高调用方法的效率提升性能。
- 没有cache机制:当对象调用方法的时候,先根据对象的isa指针去它对应的类中寻找方法,然后在类的methodLists中寻找,如果没有找到,则通过super_class指针到父类中的methodList里面去找,一旦找到方法就调用,没有找到的话就进行消息转发,或者报出异常。(效率很低)
- 有cache机制:当对象调用方法的时候,先去cache中寻找方法,如果没有找到,再依照上述方法到methodLists中查找。
<<<<<<<<<<<<<< 后续持续更新 >>>>>>>>>>>>>>>>
网友评论