前段時間看了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;
}
複製程式碼
最後放入我畫的一張圖