本文主要是解析Weak這種特性的底層實(shí)現(xiàn)。
weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)蹋辅。為這種屬性設(shè)置新值時(shí)送丰,設(shè)置方法既不保留新值怒详,也不釋放舊值。此特質(zhì)同 assign 類似烙懦, 然而在屬性所指的對(duì)象遭到摧毀時(shí)驱入,屬性值也會(huì)清空(nil out)。
以下的源碼來自于是Runtime最新版本objc4-723.tar.gz氯析。
先上一個(gè)個(gè)人整理的整體結(jié)構(gòu)圖亏较,好讓大家心中有數(shù)。
結(jié)構(gòu)體
SideTables
SideTables用來管理所有對(duì)象的引用計(jì)數(shù)和weak指針掩缓,是一個(gè)全局的Hash表雪情。它使用對(duì)象的內(nèi)存地址當(dāng)它的key,SideTable結(jié)構(gòu)體為value你辣。
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
這個(gè)有點(diǎn)難懂巡通,以下是weak 弱引用的實(shí)現(xiàn)方式的解釋:
在取出實(shí)例方法的實(shí)現(xiàn)中尘执,使用了 C++ 標(biāo)準(zhǔn)轉(zhuǎn)換運(yùn)算符 reinterpret_cast ,其表達(dá)方式為:
reinterpret_cast <new_type> (expression)
而 StripedMap 是一個(gè)模板類(Template Class)宴凉,通過傳入類(結(jié)構(gòu)體)參數(shù)誊锭,會(huì)動(dòng)態(tài)修改在該類中的一個(gè) array 成員存儲(chǔ)的元素類型,并且其中提供了一個(gè)針對(duì)于地址的 hash 算法弥锄,用作存儲(chǔ) key丧靡。可以說籽暇, StripedMap 提供了一套擁有將地址作為 key 的 hash table 解決方案温治,而該方案采用了模板類,是擁有泛型性的图仓。
SideTable
Side table 本質(zhì)就是用于保存額外信息的單獨(dú)內(nèi)存塊罐盔,并且它還是可選的但绕。也就是說救崔,對(duì)于那些無需保存額外信息的對(duì)象來說并沒有多余開銷。它主要用于管理對(duì)象的引用計(jì)數(shù)和 weak 表捏顺。
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(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
- slock: 保證原子操作的自旋鎖六孵,避免計(jì)數(shù)錯(cuò)誤。這里蘋果為了性能考慮選擇自旋鎖幅骄。
自旋鎖是一種非阻塞鎖劫窒,也就是說,如果某線程需要獲取自旋鎖拆座,但該鎖已經(jīng)被其他線程占用時(shí)主巍,該線程不會(huì)被掛起,而是在不斷的消耗CPU的時(shí)間挪凑,不停的試圖獲取自旋鎖孕索。
互斥量是阻塞鎖,當(dāng)某線程無法獲取互斥量時(shí)躏碳,該線程會(huì)被直接掛起搞旭,該線程不再消耗CPU時(shí)間,當(dāng)其他線程釋放互斥量后菇绵,操作系統(tǒng)會(huì)激活那個(gè)被掛起的線程肄渗,讓其投入運(yùn)行。
自旋鎖比較適用于鎖使用者保持鎖時(shí)間比較短的情況咬最。正是由于自旋鎖使用者一般保持鎖時(shí)間非常短翎嫡,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠(yuǎn)高于互斥鎖永乌。信號(hào)量和讀寫信號(hào)量適合于保持時(shí)間較長(zhǎng)的情況惑申,它們會(huì)導(dǎo)致調(diào)用者睡眠翁垂,因此只能在進(jìn)程上下文使用,而自旋鎖適合于保持時(shí)間非常短的情況硝桩,它可以在任何上下文使用沿猜。如果被保護(hù)的共享資源只在進(jìn)程上下文訪問,使用信號(hào)量保護(hù)該共享資源非常合適碗脊,如果對(duì)共享資源的訪問時(shí)間非常短啼肩,自旋鎖也可以。但是如果被保護(hù)的共享資源需要在中斷上下文訪問(包括底半部即中斷處理句柄和頂半部即軟中斷)衙伶,就必須使用自旋鎖祈坠。自旋鎖保持期間是搶占失效的,而信號(hào)量和讀寫信號(hào)量保持期間是可以被搶占的矢劲。自旋鎖只有在內(nèi)核可搶占或SMP(多處理器)的情況下才真正需要赦拘,在單CPU且不可搶占的內(nèi)核下,自旋鎖的所有操作都是空操作芬沉。另外格外注意一點(diǎn):自旋鎖不能遞歸使用躺同。
- refcnts:引用計(jì)數(shù)的 hash 表,通過散列表的結(jié)構(gòu)存儲(chǔ)了對(duì)象持有者的地址以及引用計(jì)數(shù)
- weak_table:weak 引用全局 hash 表
RefcountMap
有點(diǎn)復(fù)雜丸逸,想看詳細(xì)結(jié)構(gòu)的可以看這里llvm::DenseMapBase< DerivedT, KeyT, ValueT, KeyInfoT, BucketT > Class Template Reference
由于本篇文章主要不是講解引用計(jì)數(shù)原理的蹋艺,所以關(guān)于RefcountMap沒有多做研究,關(guān)于引用計(jì)數(shù)原理和RefcountMap推薦看這篇文章Objective-C 引用計(jì)數(shù)原理
weak_table_t
蘋果使用一個(gè)全局的 weak 表來保存所有的 weak 引用黄刚。使用對(duì)象地址為key捎谨,weak_entry_t結(jié)構(gòu)為value,weak_entry_t 中保存了所有指向該對(duì)象的 weak 指針憔维。
weak 表的作用是在對(duì)象執(zhí)行 dealloc 的時(shí)候?qū)⑺兄赶蛟搶?duì)象的 weak 指針的值設(shè)為 nil涛救,避免懸空指針。
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
- weak_entries:負(fù)責(zé)維護(hù)和存儲(chǔ)指向一個(gè)對(duì)象的所有弱引用的 hash 表
- num_entries:負(fù)責(zé)記錄weak_table_t目前保存的entry的數(shù)目
- mask:記錄weak_table_t的總?cè)萘?/li>
- max_hash_displacement:記錄weak_table_t所有項(xiàng)的最大偏移量业扒,因?yàn)闀?huì)有hash碰撞的情況检吆,而weak_table_t采用了開放尋址來解決,所以某個(gè)entry實(shí)際存儲(chǔ)的位置并不一定是hash函數(shù)計(jì)算出來的位置
weak_entry_t
weak_entry_t 是存儲(chǔ)在弱引用表中的一個(gè)內(nèi)部結(jié)構(gòu)體凶赁,它負(fù)責(zé)維護(hù)和存儲(chǔ)指向一個(gè)對(duì)象的所有弱引用hash表咧栗。
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;
}
}
};
- referent:被指對(duì)象的地址。前面循環(huán)遍歷查找的時(shí)候就是判斷目標(biāo)地址是否和他相等虱肄。
- referrers:可變數(shù)組,里面保存著所有指向這個(gè)對(duì)象的弱引用的地址致板。當(dāng)這個(gè)對(duì)象被釋放的時(shí)候,referrers里的所有指針都會(huì)被設(shè)置成nil咏窿。
- inline_referrers:只有4個(gè)元素的數(shù)組斟或,默認(rèn)情況下用它來存儲(chǔ)弱引用的指針。當(dāng)大于4個(gè)的時(shí)候使用referrers來存儲(chǔ)指針
weak_entry_t有一個(gè)巧妙的設(shè)計(jì)集嵌,即如果一個(gè)對(duì)象對(duì)應(yīng)的弱引用數(shù)目較少的話(<=WEAK_INLINE_COUNT萝挤,runtime把這個(gè)值設(shè)置為4)御毅,則其弱引用會(huì)被依次保存到一個(gè)inline數(shù)組里。這個(gè)inline數(shù)組的內(nèi)存會(huì)在weak_entry_t初始化的時(shí)候一并分配好怜珍,而不是需要用到的時(shí)候再去申請(qǐng)新的內(nèi)存空間端蛆,從而達(dá)到提到運(yùn)行效率的目的。此外酥泛,union中的兩個(gè)struct是共享同一塊內(nèi)存的今豆,如果不使用inline數(shù)組,而直接使用HashTable的方式來實(shí)現(xiàn)柔袁,那么num_refs呆躲,mask和max_hash_displacement這些變量都需要單獨(dú)的存儲(chǔ)空間,會(huì)使用更多的內(nèi)存捶索。綜上插掂,使用inline數(shù)組在節(jié)約一定內(nèi)存空間的同時(shí)還相對(duì)提高了運(yùn)行效率。
工作原理分析
objc_initWeak
在初始化一個(gè)weak對(duì)象時(shí)runtime會(huì)調(diào)用objc_initWeak
方法腥例,這個(gè)方法的具體實(shí)現(xiàn)是在NSObject.mm
文件中
/**
* 初始化一個(gè)指向某個(gè)對(duì)象地址的新指針辅甥。
*/
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
// 這里使用了Template(模板),有三個(gè)參數(shù)
// DontHaveOld--沒有舊對(duì)象院崇,
// DoHaveNew--有新對(duì)象肆氓,
// DoCrashIfDeallocating-- 如果newObj已經(jīng)被釋放了就Crash提示
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
// Template 參數(shù)
enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
// 更新weak變量.
// 當(dāng)設(shè)置HaveOld是true,即DoHaveOld底瓣,表示這個(gè)weak變量已經(jīng)有值,需要被清理蕉陋,這個(gè)值也有能是nil
// 當(dāng)設(shè)置HaveNew是true捐凭, 即DoHaveNew,表示有一個(gè)新值被賦值給weak變量凳鬓,這個(gè)值也有能是nil
//當(dāng)設(shè)置參數(shù)CrashIfDeallocating是true茁肠,即DoCrashIfDeallocating,如果newObj已經(jīng)被釋放或者newObj是一個(gè)不支持弱引用的類缩举,則暫停進(jìn)程
// deallocating或newObj的類不支持弱引用
// 當(dāng)設(shè)置參數(shù)CrashIfDeallocating是false垦梆,即DontCrashIfDeallocating,則存儲(chǔ)nil
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
這個(gè)函數(shù)在Clang中的聲明如下:
id objc_initWeak(id *object, id value);
前提條件:object
是一個(gè)有效的指針仅孩,尚未注冊(cè)為__weak
對(duì)象托猩。value
為null或指向有效對(duì)象的指針。
如果value
是空指針或它指向的對(duì)象已經(jīng)釋放辽慕,則object
也就是weak的指針將初始化為0(nil)京腥。 否則,將object
注冊(cè)為指向value
的__weak
對(duì)象溅蛉。 相當(dāng)于以下代碼:
id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}
舉個(gè)栗子:
NSObject *obj = [[NSObject alloc] init];
__weak NSObject *obj1 = obj;
就會(huì)編譯成
objc_initWeak(&obj1, obj);
objc_storeWeak
接下來看看objc_storeWeak
方法
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
// 初始化當(dāng)前正在 +initialize 的類對(duì)象為nil
Class previouslyInitializedClass = nil;
id oldObj;
// 聲明新舊SideTable公浪,
SideTable *oldTable;
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:
// 如果weak ptr之前弱引用過一個(gè)obj他宛,則將這個(gè)obj所對(duì)應(yīng)的SideTable取出,賦值給oldTable
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
//通過確保沒有弱引用的對(duì)象具有未初始化的 isa欠气,防止弱引用機(jī)制和 +initialize 機(jī)制之間的死鎖厅各。
if (haveNew && newObj) {
// 獲得新對(duì)象的 isa 指針
Class cls = newObj->getIsa();
// 判斷 isa 非空且已經(jīng)初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
// 解鎖新舊SideTable
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
// 如果 newObj 已經(jīng)完成執(zhí)行完 +initialize 是最理想情況
// 如果 newObj的 +initialize 仍然在線程中執(zhí)行
// (也就是說newObj的 +initialize 正在調(diào)用 storeWeak 方法)
// 通過設(shè)置previousInitializedClass以在重試時(shí)識(shí)別它。
previouslyInitializedClass = cls;
goto retry;
}
}
// 清除舊值预柒,實(shí)際上是清除舊對(duì)象weak_table中的location
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 分配新值讯检,實(shí)際上是保存location到新對(duì)象的weak_table種
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
// 如果弱引用被釋放 weak_register_no_lock 方法返回 nil
// 如果新對(duì)象存在,并且沒有使用TaggedPointer技術(shù)卫旱,在引用計(jì)數(shù)表中設(shè)置若引用標(biāo)記位
if (newObj && !newObj->isTaggedPointer()) {
// 標(biāo)記新對(duì)象有weak引用人灼,isa.weakly_referenced = true;
newObj->setWeaklyReferenced_nolock();
}
// 設(shè)置location指針指向newObj
// 不要在其他地方設(shè)置 *location。 那會(huì)引起競(jìng)爭(zhēng)顾翼。
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
其實(shí)這個(gè)方法看的不是很明白......
weak_unregister_no_lock
/**
* 解除已經(jīng)注冊(cè)的弱引用.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table 弱引用表.
* @param referent 舊對(duì)象.
* @param referrer 舊對(duì)象弱引用地址.
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
// 如果其舊對(duì)象為 nil投放,無需注銷
if (!referent) return;
// 獲取對(duì)象對(duì)應(yīng)的weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 通過地址來解除引用關(guān)聯(lián)
remove_referrer(entry, referrer);
// 判斷referrers是否為空
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 如果referrers為空,weak_table刪除entry
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
weak_register_no_lock
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent 新對(duì)象
* @param referrer weak變量地址
*/
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;
// 如果新對(duì)象為nil 或 referent 采用了TaggedPointer計(jì)數(shù)方式适贸,直接返回灸芳,不做任何操作
if (!referent || referent->isTaggedPointer()) return referent_id;
// 保證引用對(duì)象有效(沒有在析構(gòu),同時(shí)應(yīng)該支持weak引用)
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);
}
// 正在析構(gòu)的對(duì)象拜姿,不能夠被弱引用烙样,如果crashIfDeallocating為true,crash 蕊肥,并輸出日志谒获。否者返回nil
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;
}
}
// now remember it and where it is being stored
weak_entry_t *entry;
// 在 weak_table中找到referent對(duì)應(yīng)的weak_entry,并將referrer加入到weak_entry中
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 把weak變量地址,加入weak_entry_t
// 先用數(shù)組inline_referrers存儲(chǔ)壁却,滿了用哈希表referrers
// 如果referrers到了3/4容量批狱,就擴(kuò)容2倍,重新存回去
// 沒滿展东,直接存入referrers赔硫,碰撞處理(哈希值++)
append_referrer(entry, referrer);
}
else {
// 新建一個(gè)weak_entry_t,保存weak地址
weak_entry_t new_entry(referent, referrer);
// 如果weak_table滿容盐肃,進(jìn)行自增長(zhǎng)爪膊,到了3/4就擴(kuò)容1倍
weak_grow_maybe(weak_table);
// new_entry插入weak_table,碰撞處理(哈希值++)
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
// 返回新對(duì)象
return referent_id;
}
weak_clear_no_lock
當(dāng)對(duì)象被釋放砸王,清除對(duì)象的weak_entry_t推盛、設(shè)置referrers中所有的weak地址設(shè)置為nil
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//對(duì)象
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
如果entry為nil,直接return
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// 將引用歸0
weak_referrer_t *referrers;
size_t count;
// 獲取weak地址數(shù)組处硬,分為:inline_referrers與referrers
// 前面講過只有4個(gè)元素的數(shù)組小槐,默認(rèn)情況下用inline_referrers來存儲(chǔ)弱引用的指針。當(dāng)大于4個(gè)的時(shí)候使用referrers來存儲(chǔ)指針
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
// 通過for循環(huán)取出每個(gè)weak ptr的地址
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
//// 如果weak ptr確實(shí)weak引用了referent,則將weak地址設(shè)置為nil
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();
}
}
}
// 把referent的weak_entry_t也要移除出weak_table
weak_entry_remove(weak_table, entry);
}
參考文章:
Clang 8 documentation
weak 弱引用的實(shí)現(xiàn)方式
Objective-C 引用計(jì)數(shù)原理
iOS管理對(duì)象內(nèi)存的數(shù)據(jù)結(jié)構(gòu)以及操作算法--SideTables凿跳、RefcountMap件豌、weak_table_t-一
細(xì)說weak
Runtime如何實(shí)現(xiàn)weak屬性?
Objective-C 引用計(jì)數(shù)原理
OC Runtime之Weak(1)---weak_table_t
OC Runtime之Weak(2)---weak_entry_t
OC 看objc源碼認(rèn)識(shí)weak
Objective-C runtime機(jī)制(6)——weak引用的底層實(shí)現(xiàn)原理