ObjC runtime是如何實現(xiàn)weak指針的
用strong指針創(chuàng)建weak指針,系統(tǒng)會調(diào)用objc_initWeak()函數(shù),objc_initWeak中會判斷初始化表達(dá)式的右值是否為nil,如果為nil就把weak指針指向nil,否則調(diào)用storeWeak(),注意不是objc_storeWeak()
如果對weak指針賦值,則通過objc_storeWeak() 函數(shù),通過判斷weak指針是否為nil,右值是否為nil,然后調(diào)用storeWeak()
storeWeak()
聲明:
template <bool HaveOld, bool HaveNew, bool CrashIfDeallocating>
static id storeWeak(id *location, objc_object *newObj);
之所以用template是為了優(yōu)化性能
操作:
通過HaveOld判斷weak指針是否為nil,如果不是,則緩存舊值和通過&SideTables()[xxxObj];取出舊值所在的SideTables
通過HaveNew判斷newObj是否為nil,如果不是則緩存newObj的SideTables
對SideTables加鎖
如果weak指針指向的值和舊值不一樣,則代表已經(jīng)處理了或者處于多線程data race中,會解鎖并從頭再來
對newObj做處理,防止弱引用了的對象的isa指向空
如果有舊值,調(diào)用weak_unregister_no_lock解除舊值所在weak table對weak指針的持有
解除方式是通過查找舊值的SideTable的weak_table,遍歷找到并remove掉weak指針
如果有newObj
調(diào)用weak_register_no_lock綁定newObj
如果newObj使用了TaggedPointer優(yōu)化(比如NSNUmber,會指向常量所以不需要擔(dān)心內(nèi)存釋放),則直接返回
判斷[newObj allowsWeakReference]是否為NO,如果為NO代表newObj在dealloc,此時如果CrashIfDeallocating為true,就會crash
把weak指針綁定到newObj的weak table中,如果newObj沒有weak table則創(chuàng)建
如果newObj還活著,weak_register_no_lock就會返回newObj,如果newObj是否isTaggedPointer,會調(diào)用newObj的setWeaklyReferenced_nolock()標(biāo)記一下
把weak指針指向newObj
對SideTables解鎖,返回newObj
SideTable是一個C++結(jié)構(gòu)體:
struct SideTable {
spinlock_t slock;
// 保證原子操作的自旋鎖
RefcountMap refcnts;
// 引用計數(shù)的 hash 表
weak_table_t weak_table;
// weak 引用全局 hash 表
}
struct weak_table_t {
weak_entry_t *weak_entries;
// 保存了所有指向指定對象的 weak 指針,weak_entry_t是節(jié)點的結(jié)構(gòu)體
size_t num_entries;
// 存儲空間
uintptr_t mask;
// 參與判斷引用計數(shù)輔助量
uintptr_t max_hash_displacement;
// hash key 最大偏移值
};
使用一個weak指針創(chuàng)建另一個weak指針時,會調(diào)用objc_copyWeak()
讀取weak 指針時,會通過objc_loadWeakRetained(&weak指針)獲取其指向的對象(retain一次),然后把它加到autoreleasepool中
并且!!!訪問多少次weak 指針就會調(diào)用這么多次objc_loadWeakRetained和添加這么多次autoreleasepool,所以最好在block中拿到weak指針后,用一個__strong指針指向它,避免多余的操作,并且最好直接判斷一下是否為nil,是的話返回不做處理
weak指針離開作用域時,會調(diào)用objc_destroyWeak() 函數(shù)把該指針從weak table中移除
當(dāng)此對象的引用計數(shù)為0的時候會 dealloc, dealloc的最后會調(diào)用object_dispose()函數(shù),觸發(fā)objc_clear_deallocating()函數(shù)在 weak 表中獲取對象的地址對應(yīng)的weak指針數(shù)組,從而設(shè)置為 nil,并且把相關(guān)的weak指針從weak table中移除
ps1:
當(dāng)一個OC對象的dealloc函數(shù)被觸發(fā)調(diào)用的時候,是不允許對其進(jìn)行弱引用的,如果在這時弱引用會引發(fā)crash,如:
-(void)dealloc{
__weak __typeof(self) weakSelf = self;
}
ps2:
weak指針的objc_開頭的創(chuàng)建/修改函數(shù)都不是線程安全的,多線程下極低概率會導(dǎo)致data race crash
swift中的不橋接ObjC的話掂铐,weak實現(xiàn)方式會稍微有點不一樣:
這里大概講一下swift3之后的實現(xiàn):
弱指針基本等同于普通的指針。
弱引用指向?qū)ο髮嵗膕ide table地址
當(dāng)一個弱引用對象的deinit執(zhí)行后,對象并沒有被釋放双妨,且弱引用指針也沒有被賦nil。
當(dāng)弱引用執(zhí)行完deinit后叮阅,訪問弱引用對象刁品,則對象指針才會被賦nil,且目標(biāo)對象被釋放。
弱引用對象對于每一個弱引用會包含一個引用計數(shù)(unowned計數(shù)和strong計數(shù)為同一個)浩姥,且與強(qiáng)引用計數(shù)分開統(tǒng)計(但是統(tǒng)一管理的挑随,只有當(dāng)兩者都為0才會被釋放)。
這是因為Swift的計數(shù)方式默認(rèn)為InlineRefCounts勒叠,當(dāng)對象只包含strong或unowned引用時兜挨,使用InlineRefCounts進(jìn)行計數(shù)管理,當(dāng)對象擁有了weak引用眯分,InlineRefCounts會變?yōu)镾ideTableRefCounts
也就是說拌汇,swift對象的析構(gòu)和對象的釋放不一定是同時的,當(dāng)Swift對象的strong引用計數(shù)變?yōu)?但是weak計數(shù)大于0時弊决,對象會被析構(gòu)但是不會被釋放內(nèi)存
底層實現(xiàn)
系統(tǒng)在嘗試讀取weak指針時噪舀,會通過HeapObject *swift::swift_weakTakeStrong(WeakReference *ref)函數(shù)操作,該函數(shù)最終會來到HeapObject *nativeTakeStrongFromBits(WeakReferenceBits bits) 函數(shù)
在這個函數(shù)中,會通過getNativeOrNull方法從對象的side table中查詢對象的計數(shù)飘诗,當(dāng)沒有strong引用時傅联,說明該對象已經(jīng)處于DEINITING狀態(tài),函數(shù)返回nullptr疚察,接著外層函數(shù)會賦空weak引用蒸走,weak計數(shù)減一,釋放剩余的對象內(nèi)存貌嫡,并返回nil
否則將調(diào)用weak的引用自減計數(shù)函數(shù)decrementWeak()比驻,再調(diào)用refCounts的decrementWeakShouldCleanUp()函數(shù)進(jìn)行位數(shù)操作该溯,當(dāng)weak、strong别惦、unowned計數(shù)都變?yōu)?時函數(shù)會返回true狈茉,這時候系統(tǒng)會清空weak指針對應(yīng)的side table,最后回到nativeTakeStrongFromBits調(diào)用tryRetain函數(shù)來嘗試獲取對象
完整實現(xiàn)大致如下:
HeapObject *nativeTakeStrongFromBits(WeakReferenceBits bits) {
auto side = bits.getNativeOrNull();
if (side) {
side->decrementWeak();
return side->tryRetain();
} else {
return nullptr;
}
}
void decrementWeak() {
bool cleanup = refCounts.decrementWeakShouldCleanUp();
if (!cleanup)
return;
assert(refCounts.getUnownedCount() == 0);
delete this;
}
同時掸掸,添加weak對象會使對象的引用計數(shù)管理會從InlineRefCounts替換為SideTableRefCounts氯庆,這也會帶來一定的開銷,對于有性能要求的場景swift提供了unowned扰付,unowned的行為跟strong是一樣的堤撵,但不會使計數(shù)增加,代價是對象被釋放了的話羽莺,訪問unowned指針就是未定義的行為(相當(dāng)于ObjC中的訪問野指針)