前言
深入理解OC对象一文中有提到类的结构, 本文将进一步深入解析其结构。
大纲
- 结构总览
- 方法 method_t
- cache_t
1. 结构总览
由源码typedef struct objc_class *Class;
可知class就是个结构体指针,去看objc_class
定义(截取重要部分)如下:
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() const {
return bits.data();
}
}
类型 | 名称 | 解释 |
---|---|---|
cache_t | cache | 方法缓存 |
class_data_bits_t | bits | 存储信息 |
isa
和 superclass
在深入理解OC对象中已经提及,这里不再赘述。类中的相关信息存放在bits
,不过需要通过一次位运算来获取。
返回的是一个
class_rw_t
类型,源码如下(截取重要部分):
struct class_rw_t {
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
}
class_ro_t | ro | 类的相关信息 |
method_list_t | methods | 方法列表(二维数组) |
property_list_t | properties | 属性列表(二维数组) |
protocols_list_t | protocols | 协议列表(二维数组) |
methods
、properties
、protocols
分别存储了这个类的相关信息,它们都是可变的。还有个只读的ro
也存储了一些信息。
struct class_ro_t {
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
property_list_t *baseProperties;
......
}
综上所述,可以用下面一张图概括:
结构总览图
2. method_t
struct class_rw_t
中的methods
存储的是这个类的方法
types
采用的是@encode
指令表示的字符串编码。具体可参考官方文档或者网上博客。
3. cache_t
每次调用同样的方法的时候如果都去相关的类中查找,无疑是效率低下的。所以,可以将调用过的方法存储在缓存中,下一次需要调用的时候直接去缓存中查找。struct objc_class
中的cache_t cache
就是方法缓存。其存储结构为散列表
这里大致讲一下流程:
[object instanceMethod];
- 当第一次调用
instanceMethod
时,将@selector(instanceMethod)
&_mask
会得出一个下标index
。 - 则_buckets[
index
]中存储的就是该方法的bucket_t
。 - 当第二次调用
instanceMethod
时,同理将@selector(instanceMethod)
&_mask
会得出一个下标index
. - 由上面可知
步骤一
和步骤三
得出的下标index
是一样的。 - 取出_buckets[
index
]就可以找到该方法的相关信息了。
补充一:当然通过将
SEL
与上_mask
肯定会出现:SEL
不同,但是计算出来的下标相同的情况。这里apple采用的做法是下标-1的做法。
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
假设通过&运算计算出的下标为index,如果index已经缓存了别的方法,则index -= 1。如果此时index仍然缓存了别的方法,继续index-=1,一次类推。如果index=0还是没有地方缓存,则index=最大值-1。继续一次类推,知道找到为止。读取方法时也是同样逻辑。
补充二:如果缓存散列表已经满了,则会重新开辟更大的空间来存储,则之前的缓存将会被清空掉。
网友评论