Runtime源碼剖析---圖解引用計數(shù)與weak
在iOS開發(fā)過程中,會經(jīng)常使用到一個修飾詞“weak”,使用場景大家都比較清晰,用于一些對象相互引用的時候,避免出現(xiàn)強引用,對象不能被釋放,出現(xiàn)內(nèi)存泄露的問題。
weak 關(guān)鍵字的作用弱引用,所引用對象的計數(shù)器不會加一,并在引用對象被釋放的時候自動被設(shè)置為 nil。
前言
什么是引用計數(shù)?
-
Objective-C通過retainCount的機制來決定對象是否需要釋放。 每次runloop迭代結(jié)束后,都會檢查對象的retainCount,如果retainCount等于0,就說明該對象沒有地方需要繼續(xù)使用它,可以被釋放掉了。無論是手動管理內(nèi)存,還是ARC機制,都是通過對retainCount來進行內(nèi)存管理的。 - 內(nèi)存中每一個對象都有一個屬于自己的引用計數(shù)器。當(dāng)某個對象A被另一個家伙引用時,A的引用計數(shù)器就+1,如果再有一個家伙引用到A,那么A的引用計數(shù)器就再+1。當(dāng)其中某個家伙不再引用A了,A的引用計數(shù)器會-1。直到A的引用計數(shù)減到了0,那么就沒有人再需要它了,就是時候把它釋放掉了。
在引用計數(shù)中,每一個對象負責(zé)維護對象所有引用的計數(shù)值。當(dāng)一個新的引用指向?qū)ο髸r,引用計數(shù)器就遞增,當(dāng)去掉一個引用時,引用計數(shù)就遞減。當(dāng)引用計數(shù)到零時,該對象就將釋放占有的資源。
什么是循環(huán)引用?
-
循環(huán)引用發(fā)生的條件:
- 兩個對象互相強引用對方
- 該對象持有其自身(自己引用自己)
這個時候就提出了
__weak修飾符,提供弱引用,不能持有對象實例,若該對象被廢棄,則此弱引用將自動失效且處于nil被賦值的狀態(tài)
對前面兩個問題進行總結(jié):
- 如何實現(xiàn)的引用計數(shù)管理,控制加一減一和釋放?
- 如何維護weak指針防止野指針錯誤?
- 這也就是我們這篇文章要解決的主要問題,下面我們就會針對上面兩個問題一一解釋。
引用計數(shù)
引用計數(shù)的存儲
有些對象如果支持使用
TaggedPointer,蘋果會直接將其指針值作為引用計數(shù)返回;如果當(dāng)前設(shè)備是 64 位環(huán)境并且使用Objective-C 2.0,那么‘一些’對象會使用其isa指針的一部分空間來存儲它的引用計數(shù);否則Runtime會使用一張散列表來管理引用計數(shù)。
我們知道,對于純量類型的變量,是沒有引用計數(shù)的,因為不是對象,其申請的變量存儲在棧上,由系統(tǒng)來負責(zé)管理。
另外一種類型,是前面講過的
Tagged Pointer小對象,包括NSNumber、NSString、NSDate等幾個類的變量。它們也是存儲在棧上,當(dāng)然也沒有引用計數(shù)。
- 關(guān)于對象類型的引用計數(shù),其存在的兩種方式
isa指針中的引用計數(shù)
- 如果你對isa不夠熟悉的話,可以看看我這篇文章Runtime源碼剖析---圖解對象、類與isa
- 簡單來說,就是isa再32位之前為指針,在64位操作系統(tǒng)之后就不僅僅是個指針,是個聯(lián)合體,它一部分負責(zé)存儲地址,另一部分可以存儲其他的信息。這樣做可以提高了內(nèi)存的使用率、減少了不必要的開銷。
- 下面我給出isa的源碼
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
// 相當(dāng)于是unsigned long bits;
uintptr_t bits;
#if defined(ISA_BITFIELD)
// 這里的定義在isa.h中,如下(注意uintptr_t實際上就是unsigned long)
// uintptr_t nonpointer : 1; \
// uintptr_t has_assoc : 1; \
// uintptr_t has_cxx_dtor : 1; \
// uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
// uintptr_t magic : 6; \
// uintptr_t weakly_referenced : 1; \
// uintptr_t deallocating : 1; \
// uintptr_t has_sidetable_rc : 1; \
// uintptr_t extra_rc : 8
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
- 下面列出
isa指針中變量對應(yīng)的含義
| 變量名 | 含義 |
|---|---|
| indexed | 0 表示普通的 isa 指針,1 表示使用優(yōu)化,存儲引用計數(shù) |
| has_assoc | 表示該對象是否包含 associated object,如果沒有,則析構(gòu)時會更快 |
| has_cxx_dtor | 表示該對象是否有 C++ 或 ARC 的析構(gòu)函數(shù),如果沒有,則析構(gòu)時更快 |
| shiftcls | 類的指針 |
| magic | 固定值為 0xd2,用于在調(diào)試時分辨對象是否未完成初始化。 |
| weakly_referenced | 表示該對象是否有過 weak 對象,如果沒有,則析構(gòu)時更快 |
| deallocating | 表示該對象是否正在析構(gòu) |
| has_sidetable_rc | 表示該對象的引用計數(shù)值是否過大無法存儲在 isa 指針 |
| extra_rc | 存儲引用計數(shù)值減一后的結(jié)果 |
- 我們發(fā)現(xiàn)最后兩個變量,就和我們引用計數(shù)相關(guān)。
- 最后我們用一張圖來看看具體布局
Side Table里的引用計數(shù)
- Side Table 在系統(tǒng)中的結(jié)構(gòu)如下:
- 而每一個
Side Table中引用計數(shù)的結(jié)構(gòu)
- 現(xiàn)在我們只需要了解一下有這個結(jié)構(gòu)就夠了,我會在后面仔細剖析如何存儲和如何調(diào)用的。
引用計數(shù)的管理
管理引用計數(shù)的方法
獲取引用計數(shù)
非ARC環(huán)境下
- 在非ARC環(huán)境下通過
retainCount方法來獲取引用計數(shù)
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
該方法內(nèi)部調(diào)用了rootRetainCount函數(shù)
inline uintptr_t
objc_object::rootRetainCount()
{
//1.如果是Tagged Pointer,直接返回指針
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//如果是優(yōu)化后的isa
if (bits.nonpointer) {
//取出isa中的retainCount+1;
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
//如果Side Table還有存儲,則取出累加返回
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
- 如果它既不是
Tagged Point對象,isa指針中也沒有存儲引用計數(shù),這個時候就會走sidetable_retainCount這個方法。
uintptr_t
objc_object::sidetable_retainCount()
{
//先從SideTables中取出對應(yīng)的SideTable對象
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
//通過table對象中的refcnts,在通過傳入的地址,找到對應(yīng)的引用計數(shù)
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
- 這里有個疑惑,為什么要把取出來的數(shù)據(jù)進行右移位操作呢?
- 因為在
refcnts中存儲的并不是直接就是引用計數(shù),而是可以理解為一個聯(lián)合體,在他的低2位存儲其他特殊信心,引用計數(shù)從第三位開始存儲,所以取出來的時候需要右移操作。
- 因為在
ARC環(huán)境下
- 在
ARC時代除了使用Core Foundation庫的CFGetRetainCount()方法,也可以使用Runtime的_objc_rootRetainCount(id obj)方法來獲取引用計數(shù),此時需要引入<objc/runtime.h>頭文件。這個函數(shù)也是調(diào)用objc_object的rootRetainCount()方法:所以分析同上。
retain的實現(xiàn)
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
- 這個方法內(nèi)部會調(diào)用
rootRetain()方法
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
//Tagged Pointer對象直接返回
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
//transcribeToSideTable用于表示extra_rc是否溢出,默認為false
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);//將isa提取出來
newisa = oldisa;
//如果isa沒有優(yōu)化,直接從sideTable中返回引用計數(shù)
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
//如果對象正在析構(gòu),則直接返回nil
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//isa指針中的extra_rc+1;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
//有進位,表示溢出,extra_rc已經(jīng)不能存儲在isa指針中了
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
//如果不處理溢出情況,則在這里會遞歸調(diào)用一次,再進來的時候
//handleOverflow會被rootRetain_overflow設(shè)置為true,從而直接進入到下面的溢出處理流程
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
/*
溢出處理
1.先將extra_rc中引用計數(shù)減半,繼續(xù)存儲在isa中
2.同時把has_sidetable_rc設(shè)置為true,表明借用了sideTable存儲
3.將另一半的引用計數(shù),存放在sideTable中
*/
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//isa的extra_rc溢出,將一半refer count值放到SideTable中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
- 總結(jié):
- Tagged Pointer 對象,沒有 retain。
- isa 中 extra_rc 若未溢出,則累加 1。如果溢出,則將 isa 和 side table 對半存儲。
release的實現(xiàn)
inline void
objc_object::release()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
- 這個方法內(nèi)部會調(diào)用
rootRelease
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
//如果是Tagged Pointer,直接返回false,不需要dealloc
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//如果isa未優(yōu)化,直接調(diào)用sideTable的release函數(shù)
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
//將當(dāng)前isa指針中存儲的引用計數(shù)減1,如果未溢出,返回false,不需要dealloc
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
//減1下導(dǎo)致溢出,則需要從side Table借位,跳轉(zhuǎn)到溢出處理
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
//Side Table存在引用計數(shù)
if (!handleUnderflow) {
//如果不處理溢出,此處只會調(diào)用一次,handleUnderflow會被rootRelease_underflow設(shè)置為ture
//再次進入,則會直接進入溢出處理流程
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
//嘗試從SideTable中取出,當(dāng)前isa能存儲引用計數(shù)最大值的一半的引用計數(shù)
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
//如果取出引用計數(shù)大于0
if (borrowed > 0) {
//將取出的引用計數(shù)減一,并保存到isa中,保存成功返回false(未被銷毀)
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
//保存到isa中失敗,再次嘗試保存
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
//如果還是失敗,則將借出來的一半retain count,重新返回給side Table
//并且重新從2開始嘗試
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//以上流程執(zhí)行完了,則需要銷毀對象,調(diào)用dealloc
if (slowpath(newisa.deallocating)) {
//對象正在銷毀
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
//銷毀對象
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
- release總結(jié)
dealloc的實現(xiàn)
- (void)dealloc {
_objc_rootDealloc(self);
}
- 進入
_objc_rootDealloc
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
- 進入
rootDealloc
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
//如果是優(yōu)化的isa
//并且對應(yīng)的沒有關(guān)聯(lián)對象、weak應(yīng)用、以及c++析構(gòu)函數(shù)和sideTable的引用計數(shù),就直接free掉
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
//進入銷毀流程
object_dispose((id)this);
}
}
- 進入
object_dispose
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
- 接著進入
objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);//清除成員變量
if (assoc) _object_remove_assocations(obj);//移除關(guān)聯(lián)對象
obj->clearDeallocating();//將指向當(dāng)前的弱指針置為nil
}
return obj;
}
- 最后我們留下了一個懸念,如何把指向當(dāng)前的弱指針置為nil?我會在weak指針置nil這一節(jié)給大家講解
weak
在最開始我們說引用計數(shù)的存儲方式,清楚說到有兩種,一種是通過isa,另一種是通過Side Tables。那這個東西到底是什么,它又是怎么實現(xiàn)weak指針置nil的呢?接下來我們帶大家慢慢分析
SideTables
- 我們先介紹幾個知識點
HashMap(哈希表)
基于數(shù)組的一種數(shù)據(jù)結(jié)構(gòu), 通過一定的算法, 把 key 進行運算得出一個數(shù)字, 用這個數(shù)字做數(shù)組下標(biāo), 將 value 存入這個下標(biāo)對應(yīng)的內(nèi)存中.
HashTon(哈希桶)
哈希算法中計算出的數(shù)字, 有可能會重復(fù), 對于哈希值重復(fù)的數(shù)據(jù), 如何存入哈希表呢? 常用方法有閉散列和開散列等方式, 其中采用開散列方式的哈希表, 就可以稱為哈希桶. 開散列就是在哈希值對應(yīng)的位置上, 使用鏈表或數(shù)組, 將哈希值沖突的數(shù)據(jù)存入這個鏈表或者數(shù)組中, 可以提高查找效率.
- 為了管理所有對象的引用計數(shù)和
weak指針,蘋果創(chuàng)建了一個全局的SideTables,雖然名字后面有個"s"不過他其實是一個全局的Hash桶,里面的內(nèi)容裝的都是SideTable結(jié)構(gòu)體而已。它使用對象的內(nèi)存地址當(dāng)它的key。管理引用計數(shù)和weak指針就靠它了。 - 因為對象引用計數(shù)相關(guān)操作應(yīng)該是原子性的。不然如果多個線程同時去寫一個對象的引用計數(shù),那就會造成數(shù)據(jù)錯亂,失去了內(nèi)存管理的意義。同時又因為內(nèi)存中對象的數(shù)量是非常非常龐大的需要非常頻繁的操作SideTables,所以不能對整個Hash表加鎖。蘋果采用了分離鎖技術(shù)。
分離鎖和分拆鎖的區(qū)別
????降低鎖競爭的另一種方法是降低線程請求鎖的頻率。分拆鎖 (lock splitting) 和分離鎖 (lock striping) 是達到此目的兩種方式。相互獨立的狀態(tài)變量,應(yīng)該使用獨立的鎖進行保護。有時開發(fā)人員會錯誤地使用一個鎖保護所有的狀態(tài)變量。這些技術(shù)減小了鎖的粒度,實現(xiàn)了更好的可伸縮性。但是,這些鎖需要仔細地分配,以降低發(fā)生死鎖的危險。
????如果一個鎖守護多個相互獨立的狀態(tài)變量,你可能能夠通過分拆鎖,使每一個鎖守護不同的變量,從而改進可伸縮性。通過這樣的改變,使每一個鎖被請求的頻率都變小了。分拆鎖對于中等競爭強度的鎖,能夠有效地把它們大部分轉(zhuǎn)化為非競爭的鎖,使性能和可伸縮性都得到提高。
????分拆鎖有時候可以被擴展,分成若干加鎖塊的集合,并且它們歸屬于相互獨立的對象,這樣的情況就是分離鎖。
-
SideTables其實就是一個全局的哈希桶
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTables() 方法返回的是一個 StripedMap<SideTable>& 類型的引用:
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
......
};
-
StripedMap是一個模板類, 內(nèi)部維護一個大小為StripeCount的數(shù)組, 數(shù)組的成員為結(jié)構(gòu)體PaddedT,PaddedT結(jié)構(gòu)體只有一個成員value,value的類型是T. - 綜上所述:
SideTables()返回的StripedMap, 是一個value為SideTable的哈希桶(由于SideTable內(nèi)部又在維護數(shù)組, 所以這是一個哈希桶結(jié)構(gòu)), 哈希值由對象的地址計算得出.
SideTable
當(dāng)我們通過SideTables[key]來得到SideTable的時候,SideTable的結(jié)構(gòu)如下:
struct SideTable {
spinlock_t slock;//自旋鎖
RefcountMap refcnts;//存放引用計數(shù)
weak_table_t weak_table;//weak_table是一個哈希
......
};
spinlock_t:自旋鎖
- 自旋鎖
自旋鎖比較適用于鎖使用者保持鎖時間比較短的情況。正是由于自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高于互斥鎖。信號量和讀寫信號量適合于保持時間較長的情況,它們會導(dǎo)致調(diào)用者睡眠,因此只能在進程上下文使用,而自旋鎖適合于保持時間非常短的情況,它可以在任何上下文使用。
- 作用:在操作引用計數(shù)的時候?qū)?code>SideTable加鎖,避免數(shù)據(jù)錯誤
RefcountMap:存放引用計數(shù)
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
- DenseMap又是一個模版類
template<typename KeyT, typename ValueT,
bool ZeroValuesArePurgeable = false,
typename KeyInfoT = DenseMapInfo<KeyT> >
class DenseMap
: public DenseMapBase<DenseMap<KeyT, ValueT, ZeroValuesArePurgeable, KeyInfoT>,
KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> {
typedef DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable> BaseT;
typedef typename BaseT::BucketT BucketT;
friend class DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, ZeroValuesArePurgeable>;
BucketT *Buckets;
unsigned NumEntries;
unsigned NumTombstones;
unsigned NumBuckets;
};
列舉幾個比較重要的成員變量:
-
ZeroValuesArePurgeable默認值是false,但RefcountMap指定其初始化為true. 這個成員標(biāo)記是否可以使用值為 0 (引用計數(shù)為 1) 的桶. 因為空桶存的初始值就是 0, 所以值為 0 的桶和空桶沒什么區(qū)別. 如果允許使用值為 0 的桶, 查找桶時如果沒有找到對象對應(yīng)的桶, 也沒有找到墓碑桶, 就會優(yōu)先使用值為 0 的桶. -
Buckets指針管理一段連續(xù)內(nèi)存空間, 也就是數(shù)組, 數(shù)組成員是BucketT類型的對象, 我們這里將BucketT對象稱為桶(實際上這個數(shù)組才應(yīng)該叫桶, 蘋果把數(shù)組中的元素稱為桶應(yīng)該是為了形象一些, 而不是哈希桶中的桶的意思). 桶數(shù)組在申請空間后, 會進行初始化, 在所有位置上都放上空桶(桶的key為EmptyKey時是空桶), 之后對引用計數(shù)的操作, 都要依賴于桶. -
umEntries記錄數(shù)組中已使用的非空的桶的個數(shù). -
NumTombstones,Tombstone直譯為墓碑, 當(dāng)一個對象的引用計數(shù)為0, 要從桶中取出時, 其所處的位置會被標(biāo)記為Tombstone.NumTombstones就是數(shù)組中的墓碑的個數(shù). 后面會介紹到墓碑的作用. -
NumBuckets桶的數(shù)量, 因為數(shù)組中始終都充滿桶, 所以可以理解為數(shù)組大小
為什么Hash以后還需要個Map?其實蘋果采用的是分塊化的方法。
????舉個例子
????假設(shè)現(xiàn)在內(nèi)存中有16個對象。
0x0000、0x0001、...... 0x000e、0x000f
????咱們創(chuàng)建一個SideTables[8]來存放這16個對象,那么查找的時候發(fā)生Hash沖突的概率就是八分之一。
????假設(shè)SideTables[0x0000]和SideTables[0x0x000f]沖突,映射到相同的結(jié)果。SideTables[0x0000] == SideTables[0x0x000f] ==> 都指向同一個SideTable蘋果把兩個對象的內(nèi)存管理都放到里同一個SideTable中。你在這個SideTable中需要再次調(diào)用table.refcnts.find(0x0000)或者table.refcnts.find(0x000f)來找到他們真正的引用計數(shù)器。
????這里是一個分流。內(nèi)存中對象的數(shù)量實在是太龐大了我們通過第一個Hash表只是過濾了第一次,然后我們還需要再通過這個Map才能精確的定位到我們要找的對象的引用計數(shù)器。
-
引用計數(shù)結(jié)構(gòu)
bucketT實際上是std::pair,類似于isa,使用其中幾個bit來保存引用計數(shù),留出幾個bit用來做其他標(biāo)記位-
(1UL<<0) WEAKLY_REFERENCED
表示是否有弱引用指向這個對象,如果有的話(值為1)在對象釋放的時候需要把所有指向它的弱引用都變成nil(相當(dāng)于其他語言的NULL),避免野指針錯誤。
-
(1UL<<1)?DEALLOCATING
表示對象是否正在被釋放。1正在釋放,0沒有。
-
REAL COUNT
REAL COUNT的部分才是對象真正的引用計數(shù)存儲區(qū)。所以咱們說的引用計數(shù)加一或者減一,實際上是對整個unsigned long加四或者減四,因為真正的計數(shù)是從2^2位開始的。
-
(1UL<<(WORD_BITS-1))?SIDE_TABLE其中WORD_BITS
在32位和64位系統(tǒng)的時候分別等于32和64。隨著對象的引用計數(shù)不斷變大。如果這一位都變成1了,就表示引用計數(shù)已經(jīng)最大了不能再增加了。_RC_PINNED
-
RefcountMap工作邏輯
- 過計算對象地址的哈希值, 來從
SideTables中獲取對應(yīng)的SideTable. 哈希值重復(fù)的對象的引用計數(shù)存儲在同一個SideTable里. -
SideTable使用find()方法和重載 [] 運算符的方式, 通過對象地址來確定對象對應(yīng)的桶. 最終執(zhí)行到的查找算法是LookupBucketFor().
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
//如果桶的個數(shù)是0
if (NumBuckets == 0) {
FoundBucket = 0;
return false;//返回false,回上層調(diào)用添加函數(shù)
}
// FoundTombstone - Keep track of whether we find a tombstone or zero value while probing.
const BucketT *FoundTombstone = 0;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);//將哈希值與數(shù)組最大下標(biāo)按位與
unsigned ProbeAmt = 1;//哈希值重復(fù)的對象需要靠它來重新尋找位置
while (1) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;//頭指針+下標(biāo),類似于數(shù)組取值
//找到桶中的key和對象地址相等,則是找到了
if (KeyInfoT::isEqual(Val, ThisBucket->first)) {
FoundBucket = ThisBucket;
return true;
}
//找到的桶中的key是空桶占位符,則表示可插入
if (KeyInfoT::isEqual(ThisBucket->first, EmptyKey)) {
if (FoundTombstone) ThisBucket = FoundTombstone;//如果曾遇到墓碑,則使用墓碑的位置
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;//找到空占位符,則表明表中沒有已經(jīng)插入了該對象的桶
return false;
}
//如果找到了墓碑
if (KeyInfoT::isEqual(ThisBucket->first, TombstoneKey) && !FoundTombstone)
FoundTombstone = ThisBucket; //記錄下墓碑
//這里涉及到最初定義 typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap, 傳入的第三個參數(shù) true
//這個參數(shù)代表是否可以清除 0 值, 也就是說這個參數(shù)為 true 并且沒有墓碑的時候, 會記錄下找到的 value 為 0 的桶
if (ZeroValuesArePurgeable &&
ThisBucket->second == 0 && !FoundTombstone)
FoundTombstone = ThisBucket;
//用于計數(shù)的ProbeAmt如果大于數(shù)組容量,就會拋出異常
if (ProbeAmt > NumBuckets) {
_objc_fatal("Hash table corrupted. This is probably a memory error "
"somewhere. (table at %p, buckets at %p (%zu bytes), "
"%u buckets, %u entries, %u tombstones, "
"data %p %p %p %p)",
this, BucketsPtr, malloc_size(BucketsPtr),
NumBuckets, getNumEntries(), getNumTombstones(),
((void**)BucketsPtr)[0], ((void**)BucketsPtr)[1],
((void**)BucketsPtr)[2], ((void**)BucketsPtr)[3]);
}
BucketNo += ProbeAmt++;//本次哈希計算得出的下標(biāo)不符合,則利用ProbeAmt尋找下一個下標(biāo)
BucketNo&= (NumBuckets-1);//得到新的數(shù)字和數(shù)組下標(biāo)按最大值按位與
}
}
- 下面我們通過畫圖的方式來展示上述代碼的流程:這里引用小新0541的神作
首先我們有一個初始化好的, 大小為 9 的桶數(shù)組, 同時有 a b c d e 五個對象要使用桶數(shù)組, 這里我們假設(shè)五個對象都被哈希算法分配到下標(biāo) 0 的位置里. a 第一個進入, 但 b c d e 由于下標(biāo) 0 處已經(jīng)不是空桶, 則需要進行下一步哈希算法來查找合適的位置, 假設(shè)這 4 個對象又恰巧都被分配到了下標(biāo)為 1 的位置, 但只有 b 可以存入. 假設(shè)每一次哈希計算都只給下標(biāo)增加了 1, 以此類推我們能得到:
假設(shè)這個時候 c 對象被釋放了, 之前提到過這個時候會把對應(yīng)的位置的 key 設(shè)置為 TombstoneKey:
接下來就體現(xiàn)了墓碑的作用:
- 如果 c 對象銷毀后將下標(biāo) 2 的桶設(shè)置為空桶, 此時為 e 對象增加引用計數(shù), 根據(jù)哈希算法查找到下標(biāo)為 2 的桶時, 就會直接插入, 無法為已經(jīng)在下標(biāo)為 4 的桶中的 e 增加引用計數(shù).
- 如果此時初始化了一個新的對象 f, 根據(jù)哈希算法查找到下標(biāo)為 2 的桶時發(fā)現(xiàn)桶中放置了墓碑, 此時會記錄下來下標(biāo) 2. 接下來繼續(xù)哈希算法查找位置, 查找到空桶時, 就證明表中沒有對象 f, 此時 f 使用記錄好的下標(biāo) 2 的桶而不是查找到的空桶, 就可以利用到已經(jīng)釋放的位置.
- 插入對象引用計數(shù)流程
BucketT *InsertIntoBucketImpl(const KeyT &Key, BucketT *TheBucket) {
//如果哈希表的負載大于3/4,則擴展該表。
//如果只有不到1/8的桶是空的(這意味著許多桶都是由tombstone填充的),則重新哈希表而不增加。
//后一種情況比較棘手。例如,如果我們有一個空桶,有大量的tombstone,失敗的查找(例如插入)將不得不探查幾乎整個表,直到它找到空桶為止。如果表中完全填滿了tombstone,則不會成功進行任何查找,從而導(dǎo)致無限循環(huán)的查找。
unsigned NewNumEntries = getNumEntries() + 1;//桶的使用量+1
unsigned NumBuckets = getNumBuckets();//桶的總數(shù)
if (NewNumEntries*4 >= NumBuckets*3) {//使用量超過3/4
this->grow(NumBuckets * 2);//數(shù)組大小*2做參數(shù),grow中會決定具體數(shù)值
//grow中會重新布置所有桶的位置,所以將要插入的對象也要重新定位
LookupBucketFor(Key, TheBucket);
NumBuckets = getNumBuckets();//獲取最新數(shù)組的大小
}
//如果空桶數(shù)量小于1/8,哈希查找會很難定位到空桶的位置
if (NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8) {
//grow以原大小重新開辟空間,重新安排桶的位置并能清楚墓碑
this->grow(NumBuckets);
LookupBucketFor(Key, TheBucket);//重新布局后將要插入的對象也要重新確定位置
}
assert(TheBucket);
//找到的BucketT標(biāo)記了EmptyKey,可以直接使用
if (KeyInfoT::isEqual(TheBucket->first, getEmptyKey())) {
//桶數(shù)量+1
incrementNumEntries();
}
//如果找的是墓碑
else if (KeyInfoT::isEqual(TheBucket->first, getTombstoneKey())) {
incrementNumEntries();//桶的使用量+1
decrementNumTombstones();//墓碑?dāng)?shù)量-1
}
//如果找到的位置是value為0的位置
else if (ZeroValuesArePurgeable && TheBucket->second == 0) {
// Purging a zero. No accounting changes.
TheBucket->second.~ValueT();
} else {
// Updating an existing entry. No accounting changes.
}
return TheBucket;
}
weak_table_t:維護weak指針
-
weak_table是一個哈希表的結(jié)構(gòu), 根據(jù)weak指針指向的對象的地址計算哈希值, 哈希值相同的對象按照下標(biāo) +1 的形式向后查找可用位置, 是典型的閉散列算法
struct weak_table_t {
weak_entry_t *weak_entries;//連續(xù)地址空間的頭指針,數(shù)組
size_t num_entries;//數(shù)組中已經(jīng)占用位置的個數(shù)
uintptr_t mask;//數(shù)組下標(biāo)最大值(即數(shù)組大小-1)
uintptr_t max_hash_displacement;//最大哈希偏移值
};
weak_entry_t
struct weak_entry_t {
DisguisedPtr<objc_object> referent;//對象的地址
union {//這里是一個聯(lián)合體
struct {
//因為這里要存儲的又是一個weak指針數(shù)組,所以采用哈希算法
weak_referrer_t *referrers;//指向referent對象的weak指針數(shù)組
uintptr_t out_of_line_ness : 2;//這里標(biāo)記是否超過內(nèi)聯(lián)邊界
uintptr_t num_refs : PTR_MINUS_2;//數(shù)組中已占用的大小
uintptr_t mask;//數(shù)組下標(biāo)最大值(數(shù)組大小-1)
uintptr_t max_hash_displacement;//最大哈希偏移值
};
struct {
//這是一個取名急哦啊內(nèi)聯(lián)引用的數(shù)組
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];//宏定義的值是4
};
};
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_table_t中找到對應(yīng)的weak_entry_t,weak_entry_t中保存了所有指向這個對象的weak指針. - 當(dāng)指向這個對象的
weak指針不超過 4 個, 則直接使用數(shù)組inline_referrers, 省去了哈希操作的步驟, 如果weak指針個數(shù)超過了 4 個, 就要使用第一個結(jié)構(gòu)體中的哈希表. - 下面我們用一張圖來看看,
weak_table表是什么樣的結(jié)構(gòu)
- 接著我們再來看看SideTables整體結(jié)構(gòu)
weak_table_t 的工作邏輯
初始化weak指針
- 在
ARC下, 編譯器會自動添加管理引用計數(shù)的代碼,weak指針賦值的時候, 編譯器會調(diào)用storeWeak來賦值, 若weak指針有指向的對象, 那么會先調(diào)用weak_unregister_no_lock()方法來從原有的表中先刪除這個weak指針, 然后再調(diào)用weak_register_no_lock()來向?qū)?yīng)的表中插入這個weak指針.
id
objc_initWeak(id *location, id newObj)
{
//查看對象實例是否有效
//無效對象直接導(dǎo)致指針釋放
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
- 通過上面方法進行給
weak指針賦值,最終會調(diào)用storeWeak
// HaveOld: true - 變量有值
// false - 需要被及時清理,當(dāng)前值可能為 nil
// HaveNew: true - 需要被分配的新值,當(dāng)前值可能為 nil
// false - 不需要分配新值
// CrashIfDeallocating: true - 說明 newObj 已經(jīng)釋放或者 newObj 不支持弱引用,該過程需要暫停
// false - 用 nil 替代存儲
template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
// 該過程用來更新弱引用指針的指向
// 初始化 previouslyInitializedClass 指針
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
//模板函數(shù), haveOld 和 haveNew 由編譯器決定傳入的值, location 是 weak 指針, newObj 是 weak 指針將要指向的對象
retry:
if (haveOld) {
//更改指針,獲得以oldObj 為索引所存儲的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
// 更改新值指針,獲得以 newObj 為索引所存儲的值地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加鎖操作,防止多線程中競爭沖突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 避免線程沖突重處理
// location 應(yīng)該與 oldObj 保持一致,如果不同,說明當(dāng)前的 location 已經(jīng)處理過 oldObj 可是又被其他線程所修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 防止弱引用間死鎖
// 并且通過 +initialize 初始化構(gòu)造器保證所有弱引用的 isa 非空指向
if (haveNew && newObj)
// 獲得新對象的 isa 指針
Class cls = newObj->getIsa();
// 判斷 isa 非空且已經(jīng)初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
// 對其 isa 指針進行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果該類已經(jīng)完成執(zhí)行 +initialize 方法是最理想情況
// 如果該類 +initialize 在線程中
// 例如 +initialize 正在調(diào)用 storeWeak 方法
// 需要手動對其增加保護策略,并設(shè)置 previouslyInitializedClass 指針進行標(biāo)記
previouslyInitializedClass = cls;
// 重新嘗試
goto retry;
}
}
if (haveOld) {
//如果weak指針有舊值,則需要在weak_table中處理掉舊值
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
//如果weak指針將要指向新值,在weak_table中處理賦值操作
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 如果弱引用被釋放 weak_register_no_lock 方法返回 nil
// 在引用計數(shù)表中設(shè)置若引用標(biāo)記位
if (newObj && !newObj->isTaggedPointer()) {
// 弱引用位初始化操作
// 引用計數(shù)那張散列表的weak引用對象的引用計數(shù)中標(biāo)識為weak引用
newObj->setWeaklyReferenced_nolock();
}
// 之前不要設(shè)置 location 對象,這里需要更改指針指向
*location = (id)newObj;
}
else {
// 沒有新值,則無需更改
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
- 如果weak指針有舊值,則需要在weak_table中處理掉舊值,就需要用到
weak_unregister_no_lock方法
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;//weak指針指向的對象
objc_object **referrer = (objc_object **)referrer_id;//referrer_id是weak指針,操作時需要用到這個指針
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {//查找referent對象對應(yīng)的entry
remove_referrer(entry, referrer);//從referent 對應(yīng)的 entry 中刪除地址為 referrer 的 weak 指針
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {//如果 entry 中的數(shù)組容量大于 4 并且數(shù)組中還有元素
empty = false;//entry 非空
} else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {//否則循環(huán)查找 entry 數(shù)組, 如果 4 個位置中有一個非空
empty = false; //entry 非空
break;
}
}
}
if (empty) {//如果沒有通過之前的查找邏輯, 則說明 entry 為空
weak_entry_remove(weak_table, entry);//從 weak_table 中移除該條 entry
}
}
}
//接著會調(diào)用weak_entry_remove方法
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
if (entry->out_of_line()) free(entry->referrers); //如果 out_of_line(), 則需要 free 掉為 referrers alloc 的空間
bzero(entry, sizeof(*entry));//entry 所屬空間清空
weak_table->num_entries--;//weak_table 中 entries 元素個數(shù) -1
weak_compact_maybe(weak_table);//根據(jù)需要重新調(diào)整 weak_table 的空間
}
//如何重新調(diào)整weak_table空間
static void weak_compact_maybe(weak_table_t *weak_table) {
size_t old_size = TABLE_SIZE(weak_table);
//如果數(shù)組大小大于 1024, 但使用量小于 1/16 的話, 將數(shù)組進行收縮, 節(jié)省空間
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8); //收縮至原有大小的 1/8
//使用量小于 1/16, 收縮至 1/8 后, 使用量小于 1/2
}
}
- 如果
weak指針將要指向新值,在weak_table中處理賦值操作,需要運用weak_register_no_lock方法
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_allowsWeakReference);
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {//如果 weak_table 有對應(yīng)的 entry
append_referrer(entry, referrer);//將 weak 指針存入對應(yīng)的 entry 中
}
else {
weak_entry_t new_entry(referent, referrer);//創(chuàng)建新的 entry
weak_grow_maybe(weak_table);//查看是否需要調(diào)整 weak_table 中 weak_entries 數(shù)組大小
weak_entry_insert(weak_table, &new_entry);//將新的 entry 插入到 weak_table 中
}
return referent_id;
}
//通過調(diào)用append_referrer()方法將weak存入entry
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {//如果數(shù)組大小沒超過 4
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {//循環(huán)查找數(shù)組成員
entry->inline_referrers[i] = new_referrer;//把新的 weak 指針插入到空位置
return;
}
}
//數(shù)組中的 4 個位置都非空, 就要調(diào)整策略使用 referrers 了
//從這里開始, 這一段是把 inline_referrers 數(shù)組調(diào)整為使用 referrers 的形式
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));//還是開辟 4 個 weak_referrer_t 大小的空間
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];//將 inline_referrers 中的值賦值給 referrers
}
//配置 entry 結(jié)構(gòu)
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) {//數(shù)組使用量超過 3/4
return grow_refs_and_insert(entry, new_referrer);//需要擴展數(shù)組并進行插入
}
//開始哈希算法
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;//使用哈希算法計算到一個起始下標(biāo)
size_t hash_displacement = 0;//哈希偏移次數(shù)
while (entry->referrers[index] != nil) {//循環(huán)找空位置
hash_displacement++;//移位一次 +1
index = (index+1) & entry->mask;//從起始位置開始遍歷, 對數(shù)組大小取模
if (index == begin) bad_weak_table(entry);//如果找了一圈, 證明算法出了點問題
}
//這里記錄下移位的最大值, 那么數(shù)組里的任何一個數(shù)據(jù), 存儲時的移位次數(shù)都不大于這個值
//可以提升查找時的效率, 如果移位次數(shù)超過了這個值都沒有找到, 就證明要查找的項不在數(shù)組中
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++;//數(shù)組使用量 +1
}
- 下面我們用一張圖來總結(jié)初始化weak指針的流程
weak指針置nil
當(dāng)weak引用指向的對象被釋放時,我們需要把指針置為nil
- 我們在前面已經(jīng)講解了,當(dāng)一個對象釋放時,需要調(diào)用
objc_release方法,如果引用計數(shù)為0時,會執(zhí)行dealloc方法,在把weak指針置nil的過程會調(diào)用clearDeallocating
void
objc_clear_deallocating(id obj)
{
assert(obj);
if (obj->isTaggedPointer()) return;
obj->clearDeallocating();
}
- 在函數(shù)內(nèi)部調(diào)用了
clearDeallocating函數(shù)
inline void
objc_object::clearDeallocating()
{
//如果是沒有優(yōu)化過的isa指針
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
//是優(yōu)化過的isa指針,并且是否有弱引用過,或者引用計數(shù)是否存在sideTable中
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
- 不論我們是哪種方式,我們都會調(diào)用
weak_clear_no_lock方法
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//1、拿到被銷毀對象的指針
objc_object *referent = (objc_object *)referent_id;
//2、通過 指針 在weak_table中查找出對應(yīng)的entry
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
return;
}
//3、將所有的引用設(shè)置成nil
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
//3.1、如果弱引用超過4個則將referrers數(shù)組內(nèi)的弱引用都置成nil。
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
//3.2、不超過4個則將inline_referrers數(shù)組內(nèi)的弱引用都置成nil
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//循環(huán)設(shè)置所有的引用為nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
//4、從weak_table中移除entry
weak_entry_remove(weak_table, entry);
}