weak功能就不多說了,它的實現(xiàn)原理就從一段代碼開始吧。
一個OC
變量的默認(rèn)屬性都是strong
孵运,所以我們?nèi)绻枰?code>weak屬性的變量就需要顯示的標(biāo)記出來顺又。
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject *obj1 = [[NSObject alloc]init];
__weak NSObject *obj2 = obj1;
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
我們把上面這段代碼轉(zhuǎn)換成C++
就會變成這個樣子
...//一堆的函數(shù)、屬性、結(jié)構(gòu)體等等的定義返弹,不是我們關(guān)注的重點
...
//main函數(shù)的c++實現(xiàn)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj1 = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
__attribute__((objc_ownership(weak))) NSObject *obj2 = obj1;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
然后在 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
上打上斷點锈玉,進(jìn)入?yún)R編語言嘲玫,我們可以看到main
函數(shù)的匯編實現(xiàn),摘取一些看得懂的蛙吏,可以看出一些函數(shù)的調(diào)用棧
0x1047397d9 <+25>: callq 0x10473b37a ; symbol stub for: objc_autoreleasePoolPush
0x1047397de <+30>: movq 0x534b(%rip), %rdi ; (void *)0x0000000104fa3170: NSObject
0x1047397e9 <+41>: callq 0x10473b36e ; symbol stub for: objc_alloc
0x1047397ee <+46>: movq 0x51d3(%rip), %rsi ; "init"
0x1047397f5 <+53>: movq 0x382c(%rip), %rdi ; (void *)0x0000000104c5a800: objc_msgSend
0x104739815 <+85>: callq 0x10473b392 ; symbol stub for: objc_initWeak
0x104739821 <+97>: movq 0x5310(%rip), %rcx ; (void *)0x000000010473ec28: AppDelegate
0x104739828 <+104>: movq 0x5209(%rip), %rdx ; "class"
0x104739853 <+147>: callq 0x10473b350 ; symbol stub for: NSStringFromClass
0x104739858 <+152>: movq %rax, -0x68(%rbp)
0x104739865 <+165>: callq 0x10473b3b0 ; symbol stub for: objc_retainAutoreleasedReturnValue
0x104739881 <+193>: callq 0x10473b356 ; symbol stub for: UIApplicationMain
0x10473989b <+219>: callq *0x378f(%rip) ; (void *)0x0000000104c57d70: objc_release
0x1047398a8 <+232>: callq 0x10473b386 ; symbol stub for: objc_destroyWeak
0x1047398b8 <+248>: callq 0x10473b3b6 ; symbol stub for: objc_storeStrong
0x1047398c1 <+257>: callq 0x10473b374 ; symbol stub for: objc_autoreleasePoolPop
匯編語言實在看不懂泼诱,不過通過上面三段代碼的對比我們大概知道,初始化obj1
之后舷蒲,賦值給weak
屬性的obj2
時耸袜,調(diào)用了objc_initWeak
函數(shù)。
可以在objc4源碼中找到objc_initWeak
實現(xiàn)牲平,可以看到就是一個簡單的判斷之后調(diào)用了storeWeak
函數(shù)堤框,storeWeak
函數(shù)中才真正實現(xiàn)了weak
引用。
storeWeak
函數(shù)工作流程
- 在進(jìn)行真正的引用工作之前欠拾,先要做好一些列的準(zhǔn)備工作:
- 若
weak
指針有指向的對象胰锌, 先獲取weak
指針原有對象的SideTable
引用計數(shù)表; - 若被引用
newObj
有值藐窄, 則獲取newObj
的引用計數(shù)表资昧; - 對上面兩個引用計數(shù)表加鎖;
- 判斷值是否被修改荆忍,如若被修改格带,解鎖撤缴,重新開始;
- 判斷被引用對象是否完成
isa
指針初始化叽唱,如果沒完成屈呕,解鎖,重新開始
- 若
- 在準(zhǔn)備工作完成之后棺亭,會調(diào)用
weak_unregister_no_lock()
方法來從原有的表中先刪除這個weak
指針虎眨。 - 然后再調(diào)用
weak_register_no_lock()
來向?qū)?yīng)的表中插入這個weak
指針;把被應(yīng)用對象設(shè)置為弱引用表镶摘;把被引用對象的地址賦值給weak
指針指向的地址嗽桩。 - 解鎖兩個引用表,完成
weak
引用凄敢。
//定義了一個函數(shù)模版
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
//簡單的判斷碌冶,看是否有值
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
//用于暫存location指向的地址
id oldObj;
//location原值指向的引用計數(shù)表
SideTable *oldTable;
//newObj指向的引用計數(shù)表
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
///同時獲取舊值和新值的鎖
///按鎖地址排序以防止鎖排序問題
///如果舊值有修改,那么跳回retry重新做一遍流程
retry:
if (haveOld) {
oldObj = *location;
//獲取location原指向地址的引用計數(shù)表
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
//獲取newObj的引用計數(shù)表
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
//對兩個引用計數(shù)表加鎖
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
//如果location有值而且location != oldObj,那就是location的值被改變了(因為oldObj = *location;這一步已經(jīng)賦值給oldObj,理論上應(yīng)該是相等的)涝缝,需要解鎖扑庞,從頭開始
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
//為了防止弱引用機(jī)制和初始化機(jī)制之間的死鎖,
//我們要保證被弱引用的對象isa指針已經(jīng)完成初始化工作
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// If this class is finished with +initialize then we're good.
// If this class is still running +initialize on this thread
// (i.e. +initialize called storeWeak on an instance of itself)
// then we may proceed but it will appear initializing and
// not yet initialized to the check above.
// Instead set previouslyInitializedClass to recognize it on retry.
previouslyInitializedClass = cls;
goto retry;
}
}
// Clean up old value, if any.
//把location原有的弱引用清除掉
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
//將location添加到newObj的弱引用表中
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
//如果添加弱引用被拒絕拒逮,weak_register_no_lock會返回nil
// Set is-weakly-referenced bit in refcount table.
// 將newObj設(shè)置為被弱引用狀態(tài)
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
// 只能在這里把location指向新值(newObj)
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
//對兩個引用表解鎖
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
上面這段代碼就是runtime
實現(xiàn)weak
引用的全部過程罐氨,其中有幾個數(shù)據(jù)結(jié)構(gòu)非常重要,分別是SideTables
消恍,SideTable
岂昭,weak_table_t
和weak_entry_t
。它們和對象的引用計數(shù)狠怨,以及weak引用相關(guān)约啊。
先說一下這四個數(shù)據(jù)結(jié)構(gòu)的關(guān)系。 SideTables
是一個64個元素長度的hash數(shù)組佣赖,里面存儲了SideTable
恰矩。SideTables
的hash鍵值就是一個對象obj的address。
因此可以說憎蛤,一個obj外傅,對應(yīng)了一個SideTable
。但是一個SideTable
俩檬,會對應(yīng)多個obj萎胰。因為SideTable
的數(shù)量只有64個,所以會有很多obj共用同一個SideTable
棚辽。
而在一個SideTable
中技竟,有三個成員,分別是
spinlock_t slock; //自旋鎖屈藐,用于對SideTable操作時將其鎖定
RefcountMap refcnts; //對象引用計數(shù)相關(guān)信息
weak_table_t weak_table; //對象的弱引用相關(guān)信息
其中榔组,refcents
是一個hash map熙尉,其key是obj的地址,而value搓扯,則是obj對象的引用計數(shù)检痰。而weak_table
則存儲了弱引用obj的指針的地址,其本質(zhì)是一個以obj地址為key锨推,弱引用obj的指針的地址作為value的hash表铅歼。hash表的節(jié)點類型是weak_entry_t。
SideTables
先來說一下最外層的SideTables
爱态。SideTables
可以理解為一個全局的hash數(shù)組谭贪,里面存儲了SideTable
類型的數(shù)據(jù),其長度為64锦担。
但是SideTabls
并不是一個被定義的數(shù)據(jù)類型,它只是一個全局靜態(tài)函數(shù)慨削,返回值是一個StripedMap
類型洞渔,所以其實SideTables
類型就是StripedMap
類型
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
看一下StripedMap
類的數(shù)據(jù)結(jié)構(gòu)
//參數(shù)模版化,這里討論的T為SideTable
template<typename T>
class StripedMap {
// TARGET_OS_IPHONE 目前寫死為0缚态,所以StripeCount只能是64
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
// T(模版參數(shù)磁椒,這里就是SideTable) 64字節(jié)對齊
T value alignas(CacheLineSize);
};
//所有PaddedT struct 類型數(shù)據(jù)被存儲在一個長度為64的array數(shù)組中
PaddedT array[StripeCount];
// 該方法以void *作為key 來獲取void *對應(yīng)在StripedMap中的下標(biāo)位置
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;// % StripeCount 是為了防止下標(biāo)越界
}
public:
//獲取void *對應(yīng)的SideTable
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
....//一些其他操作
};
這里的邏輯代碼寫的很清晰,我后面省略了一系列的鎖操作玫芦,可以自己在runtime
的代碼里看一下浆熔。
從中可以看到,所有的StripedMap
鎖操作桥帆,最終是調(diào)用的array[i].value
的相關(guān)操作医增。因此,對于模版參數(shù)T類型老虫,必須具備相關(guān)的lock
操作接口叶骨。
因此,要作為StripedMap
哈希表的模版參數(shù)祈匙,對于T類型還是有所要求的(就是能夠進(jìn)行鎖操作)忽刽。而在SideTables
中,T即為SideTable
類型夺欲。
SideTable
SideTable
的定義很清晰跪帝,有三個成員:
-
spinlock_t slock
: 自旋鎖,用于上鎖/解鎖SideTable
些阅。 -
RefcountMap refcnts
:以DisguisedPtr<objc_object>
為key的哈希表伞剑,用來存儲OC對象的引用計數(shù)(僅在未開啟isa優(yōu)化 或 在isa優(yōu)化情況下isa_t的引用計數(shù)溢出時才會用到)。 -
weak_table_t weak_table
: 存儲對象弱引用指針的哈希表扑眉。是OC weak
功能實現(xiàn)的核心數(shù)據(jù)結(jié)構(gòu)纸泄。
除了三個成員外赖钞,蘋果為SideTable
還寫了構(gòu)造和析構(gòu)函數(shù),在析構(gòu)函數(shù)的源碼中可以看出來聘裁,SideTable
是不能被析構(gòu)的雪营。
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
最后是一堆鎖操作,用于多線程訪問SideTable
衡便, 同時献起,也符合我們上面提到的StripedMap
中關(guān)于T模版參數(shù)的lock
接口定義。
struct SideTable {
spinlock_t slock; // 自旋鎖镣陕,防止多線程訪問沖突
RefcountMap refcnts; // 對象引用計數(shù)表
weak_table_t weak_table; // 對象弱引用表
//構(gòu)造函數(shù)
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
//析構(gòu)函數(shù)--不能析構(gòu)
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
//一系列的鎖操作
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
// 按鎖地址順序?qū)蓚€SideTable加鎖谴餐,以防止鎖排序問題
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
spinlock_t slock
spinlock_t
的最終定義實際上是一個uint32_t
類型的非公平的自旋鎖。所謂非公平呆抑,就是說獲得鎖的順序和申請鎖的順序無關(guān)岂嗓,也就是說,第一個申請鎖的線程有可能會是最后一個獲得到該鎖鹊碍,或者是剛獲得鎖的線程會再次立刻獲得到該鎖厌殉,造成饑餓等待。 同時侈咕,在OC中公罕,_os_unfair_lock_opaque
也記錄了獲取它的線程信息,只有獲得該鎖的線程才能夠解開這把鎖耀销。
typedef struct os_unfair_lock_s {
uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;
關(guān)于自旋鎖的實現(xiàn)楼眷,蘋果并未公布,但是大體上應(yīng)該是通過操作_os_unfair_lock_opaque
這個uint32_t
的值熊尉,當(dāng)大于0時罐柳,鎖可用,當(dāng)?shù)扔诨蛐∮?時帽揪,需要鎖等待硝清。
RefcountMap refcnts
可以看這個
RefcountMap refcnts
用來存儲OC對象的引用計數(shù)。它實質(zhì)上是一個以objc_object
為key的哈希表转晰,其vaule
就是OC對象的引用計數(shù)芦拿。同時,當(dāng)OC對象的引用計數(shù)變?yōu)?時查邢,會自動將相關(guān)的信息從哈希表中剔除蔗崎。
RefcountMap
的本質(zhì)是一個DenseMap
類,本文中的調(diào)用模板的三個類型參數(shù)DisguisedPtr<objc_object>
扰藕,size_t
缓苛, true
分別表示DenseMap
的hash key
類型,value
類型,是否允許引用計數(shù)為0的節(jié)點被使用未桥。
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>
-
ZeroValuesArePurgeable
默認(rèn)值是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ù)組在申請空間后勒庄,會進(jìn)行初始化串前,在所有位置上都放上空桶(桶的 key 為EmptyKey
時是空桶),之后對引用計數(shù)的操作实蔽,都要依賴于桶。
桶的數(shù)據(jù)類型實際上是std::pair
谨读,類似于swift
中的元祖類型局装,就是將對象地址和對象的引用計數(shù)(這里的引用計數(shù)類似于 isa,也是使用其中的幾個 bit 來保存引用計數(shù)劳殖,留出幾個 bit 來做其它標(biāo)記位)組合成一個數(shù)據(jù)類型铐尚。 -
NumEntries
記錄數(shù)組中已使用的非空的桶的個數(shù)。 -
NumTombstones
Tombstone
直譯為墓碑, 當(dāng)一個對象的引用計數(shù)為0哆姻,要從桶中取出時宣增,其所處的位置會被標(biāo)記為Tombstone
。NumTombstones
就是數(shù)組中的墓碑的個數(shù)矛缨。后面會介紹到墓碑的作用爹脾。 -
NumBuckets
桶的數(shù)量,因為數(shù)組中始終都充滿桶箕昭,所以可以理解為數(shù)組大小灵妨。
RefcountMap 的工作邏輯
- 通過計算對象地址的哈希值, 來從
SideTables
中獲取對應(yīng)的SideTable
. 哈希值重復(fù)的對象的引用計數(shù)存儲在同一個SideTable
里. -
SideTable
使用find()
方法和重載 [] 運算符的方式, 通過對象地址來確定對象對應(yīng)的桶. 最終執(zhí)行到的查找算法是LookupBucketFor()
. - 查找算法會先對桶的個數(shù)進(jìn)行判斷, 如果桶數(shù)為 0 則
return false
回上一級調(diào)用插入方法. 如果查找算法找到空桶或者墓碑桶, 同樣return false
回上一級調(diào)用插入算法, 不過會先記錄下找到的桶. 如果找到了對象對應(yīng)的桶, 只需要對其引用計數(shù)+ 1
或者- 1
. 如果引用計數(shù)為 0 需要銷毀對象, 就將這個桶中的 key 設(shè)置為TombstoneKey
- 插入算法會先查看可用量, 如果哈希表的可用量(墓碑桶+空桶的數(shù)量)小于 1/4, 則需要為表重新開辟更大的空間, 如果表中的空桶位置少于 1/8 (說明墓碑桶過多), 則需要清理表中的墓碑. 以上兩種情況下哈希查找算法會很難查找正確位置, 甚至可能會產(chǎn)生死循環(huán), 所以要先處理表, 處理表之后還會重新分配所有桶的位置, 之后重新查找當(dāng)前對象的可用位置并插入. 如果沒有發(fā)生以上兩種情況, 就直接把新的對象的引用計數(shù)放入調(diào)用者提供的桶里.
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
...
if (NumBuckets == 0) { //桶數(shù)是0
FoundBucket = 0;
return false; //返回 false 回上層調(diào)用添加函數(shù)
}
...
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;
return false; //找到空占位符, 則表明表中沒有已經(jīng)插入了該對象的桶
}
//如果找到了墓碑
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("...");
}
BucketNo += ProbeAmt++; //本次哈希計算得出的下表不符合, 則利用 ProbeAmt 尋找下一個下標(biāo)
BucketNo&= (NumBuckets-1); //得到新的數(shù)字和數(shù)組下標(biāo)最大值按位與
}
}
weak_table_t weak_table
weak_table_t weak_table
用來存儲OC對象弱引用的相關(guān)信息。我們知道落竹,SideTables
一共只有64個節(jié)點泌霍,而在我們的APP中,一般都會不只有64個對象述召,因此朱转,多個對象一定會重用同一個SideTable
節(jié)點蟹地,也就是說,一個weak_table
會存儲多個對象的弱引用信息藤为。因此在一個SideTable
中怪与,又會通過weak_table
作為哈希表再次分散存儲每一個對象的弱引用信息。
weak_table_t
是一個哈希表的結(jié)構(gòu), 根據(jù)對象的地址計算哈希值, 哈希值相同的對象按照下標(biāo) +1 的形式向后查找可用位置, 是典型的閉散列算法. 最大哈希偏移值即是所有對象中計算出的哈希值和實際插入位置的最大偏移量, 在查找時可以作為循環(huán)的上限.
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*
* 全局若引用表凉蜂,以objct為key琼梆,weak_entry_t做為值
*/
struct weak_table_t {
weak_entry_t *weak_entries; //hash數(shù)組,用來存儲弱引用對象的相關(guān)信息weak_entry_t
size_t num_entries; // hash數(shù)組中的元素個數(shù)
uintptr_t mask; // hash數(shù)組長度-1窿吩,會參與hash計算茎杂。(注意,這里是hash數(shù)組的長度纫雁,而不是元素個數(shù)煌往。比如,數(shù)組長度可能是64轧邪,而元素個數(shù)僅存了2個)
uintptr_t max_hash_displacement; // 可能會發(fā)生的hash沖突的最大次數(shù)
};
通過對象的地址刽脖,可以在weak_table_t
中找到對應(yīng)的 weak_entry_t
,weak_entry_t
中保存了所有指向這個對象的弱引用信息忌愚。
尋找的過程主要在weak_entry_for_referent()
函數(shù)中:
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask; // 根據(jù)ref的地址獲取hash值曲管,然后和mask按位與運算,保證不越界
size_t index = begin;
size_t hash_displacement = 0; // hash沖突位移次數(shù)
while (weak_table->weak_entries[index].referent != referent) { // 循環(huán)判斷weak_entry_t中的referent 是否與要找的referent相同
index = (index+1) & weak_table->mask; // 下標(biāo)+1并和mask按位與運算硕糊,保證數(shù)組不越界
if (index == begin) bad_weak_table(weak_table->weak_entries); // 回到初始位置還沒找到對應(yīng)的referent院水,說明weak_table有問題,拋出異常
hash_displacement++; // 位移次數(shù)+1
if (hash_displacement > weak_table->max_hash_displacement) {
//位移超過hash沖突最大次數(shù)简十,說明沒找到對應(yīng)的weak_entry_t檬某,返回空
return nil;
}
}
return &weak_table->weak_entries[index];
}
weak_entry_t
中使用了一個共用體, 當(dāng)指向這個對象的weak
指針不超過 4 個, 則直接使用數(shù)組inline_referrers
螟蝙,省去了哈希操作的步驟恢恼,如果weak
指針個數(shù)超過了 4 個,就要使用第一個結(jié)構(gòu)體中的哈希表胰默。第一個結(jié)構(gòu)體的結(jié)構(gòu)和weak_table_t
很像场斑,同樣也是一個哈希表,其存儲的元素是weak_referrer_t
初坠,實質(zhì)上是弱引用該對象的指針的指針和簸,即objc_object **new_referrer
, 通過操作指針的指針碟刺,就可以使得weak
引用的指針在對象析構(gòu)后锁保,指向nil
。
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被引用的對象
// 引用該對象的弱引用共用體。
// 引用個數(shù)小于等于4爽柒,用inline_referrers數(shù)組吴菠。
// 引用個數(shù)大于4,用哈希數(shù)組weak_referrer_t *referrers
union {
struct {
weak_referrer_t *referrers; // 弱引用該對象的指針地址的哈希數(shù)組
uintptr_t out_of_line_ness : 2; // 是否使用動態(tài)哈希數(shù)組標(biāo)記位
uintptr_t num_refs : PTR_MINUS_2; // 哈希數(shù)組中的元素個數(shù)
uintptr_t mask; // 哈希數(shù)組長度-1浩村,會參與hash計算做葵。(和weak_table_t的mask一樣)。
uintptr_t max_hash_displacement; // 可能會發(fā)生的hash沖突的最大次數(shù)
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
// 弱引用數(shù)量不超過4個時存放弱引用指針的數(shù)組
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
//是否使用動態(tài)哈希數(shù)組
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
的核心功能就是就是weak
指針的增加和刪除功能心墅,看一下增加功能--append_referrer()
/**
*
* 將給定的引用添加到entry中的弱指針集合中酿矢。不查重(b/c弱指針從不添加到集合兩次)。
*
* @param entry The entry holding the set of weak pointers.
* @param new_referrer The new weak pointer to be added.
*/
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
//判斷out_of_line_ness標(biāo)記怎燥,是否已經(jīng)使用哈希數(shù)組
//如果還沒使用就直接循環(huán)數(shù)組瘫筐,找到空位置,把弱引用指針添加進(jìn)去
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
//如果數(shù)組中已經(jīng)滿了, 就要使用動態(tài)哈希數(shù)組referrers了
//從這里開始, 這一段是把inline_referrers數(shù)組調(diào)整為使用referrers的形式
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
//配置weak_entry_t的參數(shù)
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());
//根據(jù)哈希數(shù)組的規(guī)則铐姚,使用量超過填裝因子(一般0.7-0.8)策肝,就要擴(kuò)容
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 數(shù)組使用量超過3/4
return grow_refs_and_insert(entry, new_referrer); //需要擴(kuò)展數(shù)組并配置entry信息
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask); //使用哈希算法計算到一個起始下標(biāo)
size_t index = begin;
size_t hash_displacement = 0; //哈希偏移次數(shù)
while (entry->referrers[index] != nil) {
hash_displacement++; // 便宜次數(shù)+1
index = (index+1) & entry->mask; // 下標(biāo)值+1蹲蒲,并與mask安位與運算
if (index == begin) bad_weak_table(entry); //如果找了一圈沒找到空位置斩狱,說明這個entry有問題,拋出異常
}
if (hash_displacement > entry->max_hash_displacement) { //判斷位移數(shù)是否大于原有的哈希沖突次數(shù)蝇率,如果超過就把新的偏移數(shù)重新賦值給哈希沖突數(shù)
entry->max_hash_displacement = hash_displacement;
}
// 把弱引用指針添加到referrers數(shù)組
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
// 引用次數(shù)+1
entry->num_refs++;
}
接著是刪除功能--remove_referrer()
依许,基本上也和增加功能沒什么區(qū)別:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) { //判斷out_of_line_ness標(biāo)記棺禾,是否已經(jīng)使用哈希數(shù)組
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
// 循環(huán)判斷,找到對應(yīng)的ref峭跳,將其置空
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
//如果循環(huán)一遍還沒找到對應(yīng)的弱引用帘睦,說明出bug了,拋出異常
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
// 使用hash數(shù)組的情況
size_t begin = w_hash_pointer(old_referrer) & (entry->mask); // 根據(jù)old_referrer 找到初始下標(biāo)
size_t index = begin;
size_t hash_displacement = 0; // 哈希沖突位移次數(shù)
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask; // 位移次數(shù)和mask按位與運算坦康,保證不越界
if (index == begin) bad_weak_table(entry); //循環(huán)一圈未找到,說明有bug
hash_displacement++; // 位移次數(shù)+1
if (hash_displacement > entry->max_hash_displacement) {
// 如果位移次數(shù)超過了entry標(biāo)記的最大沖突次數(shù)诡延,說明有問題滞欠,拋出異常
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
// 刪除下標(biāo)對應(yīng)的弱引用
entry->referrers[index] = nil;
// 引用次數(shù)-1
entry->num_refs--;
}
總結(jié)
調(diào)用storeWeak()
函數(shù) --> 獲取oldTable
、newTable
兩個引用計數(shù)表 --> 調(diào)用weak_unregister_no_lock()
函數(shù)刪除掉oldObj
的弱引用 --> 先調(diào)用weak_entry_for_referent()
找到弱引用信息表 --> 在調(diào)用remove_referrer()
刪除弱引用信息 --> 調(diào)用weak_register_no_lock()
把新的弱引用信息添加到newObj
的弱引用表 --> 調(diào)用weak_entry_for_referent()
找到newObj
的弱引用信息表 --> 調(diào)用append_referrer()
把location
添加到弱引用表中 --> 把newObj
賦值給location