上一篇文章主要介绍了弱引用的基本概念,以及weak_table_t的基本结构和实现。weak_entry_t是weak_table_t具体存储的数据类型,本文继续深挖,探究weak_entry_t的数据结构和实现。
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
weak_entry_t& operator=(const weak_entry_t& other) {
memcpy(this, &other, sizeof(other));
return *this;
}
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
};
DisguisedPtr是runtime对于普通对象指针(引用)的一个封装,目的在于隐藏weak_table_t的内部指针。其定义如下:
typedef DisguisedPtr<objc_object *> weak_referrer_t;
可以看出,weak_entry_t 的结构和weak_table_t有一点点类似,基本也是一个HashTable的实现。num_refs,mask和max_hash_displacement都在weak_table_t中出现过,作用也是基本相同,不再详细介绍。
weak_entry_t有一个巧妙的设计,即如果一个对象对应的弱引用数目较少的话(<=WEAK_INLINE_COUNT,runtime把这个值设置为4),则其弱引用会被依次保存到一个inline数组里。这个inline数组的内存会在weak_entry_t初始化的时候一并分配好,而不是需要用到的时候再去申请新的内存空间,从而达到提到运行效率的目的。此外,union中的两个struct是共享同一块内存的,如果不使用inline数组,而直接使用HashTable的方式来实现,那么num_refs,mask和max_hash_displacement这些变量都需要单独的存储空间,会使用更多的内存。综上,使用inline数组在节约一定内存空间的同时还相对提高了运行效率。
out_of_line_ness占用的是inline_referrers[1]最低两位的空间,也是weak_entry_t当前使用inline数组还是outline数组(也就是HashTable的实现)的标记位。由于DisguisedPtr的最低两个bit保证是00或者11(DisguisedPtr的实现此处不不表,不影响对于weak_entry_t的理解),所以如果out_of_line_ness不是0的话,也就意味着weak_entry_t已经不再使用inline数组。这一点从out_of_line函数的实现可以得到证实,需要判断out_of_line_ness的值等于REFERRERS_OUT_OF_LINE。REFERRERS_OUT_OF_LINE在runtime中被设置为2,可以被两个bit来存储表示。
对weak_entry_t主要操作的函数是grow_refs_and_insert和append_referrer两个。上一篇文章提到的weak_table_t中有一个weak_register_no_lock函数,其中就调用了append_referrer函数来为一个对象保存新的弱引用。
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
assert(entry->out_of_line());
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
- append_referrer函数首先处理weak_entry_t还在使用inline数组的情况。首先尝试像inline数组中插入一个新的弱引用,如果inline数组已满,那就创建一个WEAK_INLINE_COUNT大小的新数组,改用outline的方式,将inline数组中的元素依次拷贝过来。
- 函数的后半部分处理使用outline数组的情况,如果outline数组的使用率在75%及以上,那么调用grow_refs_and_insert函数进行扩充,并且插入新的弱引用。否则就直接进行hash运算插入,过程和weak_table_t的插入过程基本相同。
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
assert(entry->out_of_line());
size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[i]);
num_refs--;
}
}
// Insert
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
- 函数首先对outline数组进行扩充,容量是原来的两倍。而后依次将老数组中的元素hash插入到新数组中,最终hash插入新的引用。
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
- remove_referrer函数负责删除一个弱引用。函数首先处理inline数组的情况,直接将对应的弱引用项置空。如果使用了outline数组,则通过hash找到要删除的项,并直接删除。过程和weak_table_t对应的操作基本相同。
至此,weak_entry_t的数据结构和操作都介绍完毕。可以看出苹果对于程序的效率以及内存优化还是花费了很大心思的。后续会介绍NSObject实现中和弱引用有关联的部分,这些加起来构成了整个弱引用机制。
网友评论