前段時間看了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