前段时间看了iOS管理对象内存的数据结构以及操做算法后感受受益良多,因此对照源码进行了一遍本身的梳理。 ###weak实现原理 1.为了管理全部对象的引用计数和weak指针,建立了一个全局的SideTables。这是一个Hash表,里面装的是SideTable,用对象地址内存地址做为key进行散列。苹果内部将整个SideTables分为64分,因此就有64个SideTable。 SideTable结构以下:c++
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, ,存储的是弱引用对象的地址。bash
// 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
里。markdown
介绍完基本构成以后咱们再来看看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:ide
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:oop
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; } 复制代码
最后放入我画的一张图 ui