weak实现原理

2,766 阅读3分钟

前段时间看了iOS管理对象内存的数据结构以及操作算法后感觉受益良多,所以对照源码进行了一遍自己的梳理。 ###weak实现原理 1.为了管理所有对象的引用计数和weak指针,创建了一个全局的SideTables。这是一个Hash表,里面装的是SideTable,用对象地址内存地址作为key进行散列。苹果内部将整个SideTables分为64分,所以就有64个SideTable。 SideTable结构如下:

struct SideTable {
//加锁,保证线程安全
spinlock_t slock;
/*一张记录引用计数器的散列表。
*/
RefcountMap refcnts;
weak_table_t weak_table;

SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}

~SideTable() {
_objc_fatal("Do not delete SideTable.");
}

void lock() { slock.lock(); }
void unlock() { slock.unlock(); }

// Address-ordered lock discipline for a pair of side tables.

template<bool HaveOld, bool HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<bool HaveOld, bool HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

通过table.refcnts.find(this)找到对象的真正引用计数器ref,ref是size_t类型的,然后通过bit mask进行内容存储

// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0) //是否有弱指针指向这个对象,1代表有
#define SIDE_TABLE_DEALLOCATING      (1UL<<1)  // 是否正在被销毁,1代表是
#define SIDE_TABLE_RC_ONE            (1UL<<2)  //真正的引用计数
#define SIDE_TABLE_RC_PINNED         (1UL<<(WORD_BITS-1)) //最大的引用计数次数

然后我们看看SideTabel中的weak_table_t weak_table,它是如下结构

struct weak_table_t {
weak_entry_t *weak_entries; //一个放置weak_entry_t的数组
size_t    num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};

//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;
}
}
};

在weak_entry_t中,我们看到一个类型为DisguisedPtr,名为referent的指针,这里的被指向对象的地址,存储的是我看到对这个变量苹果的注释如下

//DisguisedPtr<T> acts like pointer type T*, except the
// stored value is disguised to hide it from tools like `leaks`.

说是对指针的一种封装,目的是防止泄露。 接下来是weak_referrer_t, ,存储的是弱引用对象的地址。

// The address of a __weak variable.
// These pointers are stored disguised so memory analysis tools
// don't see lots of interior pointers from the weak table into objects.
typedef DisguisedPtr<objc_object *> weak_referrer_t;

至于weak_referrer_t inline_referrers 当弱引用对象不多于4个时候,实际弱引用对象的地址存在这里面的,多余4个则存referrers里。

介绍完基本构成之后我们再来看看retain,release,retainCount这些操作是怎么实现的。(这里不讨论alloc 是因为alloc只涉及到内存的分配和isa的初始化,与上文讲的无关) retain:

//下面是对对象进行retain,可以看到是如何实现引用计数加1的
id objc_object::sidetable_retain()
{
//isa指针不能是taggedPointer(若是,就会在isa中进行引用计数的存储)
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];

table.lock();
//从散列表中获取这个size_t
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//当小于最大引用计数,引用计数+1 (这里是二进制加法)
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();

return (id)this;
}

reaintCount:

uintptr_t objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];

//引用计数初始化为1
size_t refcnt_result = 1;

table.lock();
//迭代获取RefcountMap,key为objc_object也就是内存地址,value为引用计数
RefcountMap::iterator it = table.refcnts.find(this);
//当找到RefcountMap
if (it != table.refcnts.end()) {
// c++语法,在迭代器中,first代表key,second代表value 。所以it->second 取的value也就是引用计数,然后右移两位再+1,这是因为低两位记录了其他状态,见上文
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
//所以获取的引用计数>=1
return refcnt_result;
}

release:

uintptr_t objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];

bool do_dealloc = false;

table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
//假如迭代器没有找到RefcountMap,结果标记为false,size_t标记为释放中
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// 当引用计数为0且size_t没有被标记为释放中时,进行标记
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
//没有超过最大引用计数时,且引用计数不为0时引用计数减1(二进制减法)
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc  &&  performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}

最后放入我画的一张图

SideTables结构.png