在Objective-C中,一般為了解決循環(huán)引用的問題,我們會使用weak 修飾,使得一方不會持有另一方,解決循環(huán)引用的問題.
今天就從Objective-C的源碼中看一下weak是怎么被實現(xiàn)的,在NSObject.mm文件中可以找到一個這樣的函數(shù)
/** * Initialize a fresh weak pointer to some object location.?
?* It would be used for code like: *?
* (The nil case) * __weak id weakPtr;?
* (The non-nil case) * NSObject *o = ...;
?* __weak id weakPtr = o; *?
?* This function IS NOT thread-safe with respect to concurrent
?* modifications to the weak variable. (Concurrent weak clear is safe.) *
?* @param location Address of __weak ptr. *?
@param newObj Object ptr. */
id objc_initWeak(id *location, id newObj){
?if (!newObj) { *location = nil; return nil; }?
?return storeWeak(location, (objc_object*)newObj);
}
id objc_initWeakOrNil(id *location, id newObj){
?if (!newObj) { *location = nil; return nil; }?
?return storeWeak (location, (objc_object*)newObj);
}
從注釋中可以看到weak變量會調(diào)用這個兩個方法其中一個進(jìn)行初始化.然后我們接著看后面的storeWeak方法
This function stores a new value into a __weak variable. It would be used anywhere a __weak variable is the target of an assignment
id objc_storeWeak(id *location, id newObj)
從注釋中可以很明顯看出這個方法就是用來存儲weak變量的
下面是方法的具體實現(xiàn):
// If HaveOld is true, the variable has an existing value
//? that needs to be cleaned up. This value might be nil.
// If HaveNew is true, there is a new value that needs to be
//? assigned into the variable. This value might be nil.
// If CrashIfDeallocating is true, the process is halted if newObj is
//? deallocating or newObj's class does not support weak references.
//? If CrashIfDeallocating is false, nil is stored instead.
id?objc_storeWeak(id?*location,?id?newObj)
{
????id?oldObj;
????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:
????oldObj?=?*location;
//獲取SideTable
????oldTable?=?SideTable::tableForPointer(oldObj);
????newTable?=?SideTable::tableForPointer(newObj);
????......
????if(*location?!=?oldObj)?{
????????OSSpinLockUnlock(lock1);
#if?SIDE_TABLE_STRIPE?>?1
????????if(lock1?!=?lock2)?OSSpinLockUnlock(lock2);
#endif
????????goto?retry;
????}
????if(oldObj)?{
//清空老值
????????weak_unregister_no_lock(&oldTable->weak_table,?oldObj,?location);
????}
????if(newObj)?{
//存儲新值
????????newObj?=?weak_register_no_lock(&newTable->weak_table,?newObj,location);
????????//?weak_register_no_lock?returns?NULL?if?weak?store?should?be?rejected
????}
????//?Do?not?set?*location?anywhere?else.?That?would?introduce?a?race.
????*location?=?newObj;
????......
????returnnewObj;
}
首先看注釋得到的答案是如果這個變量有老值,會先清除,如果是新值會存儲起來(這個值可以為nil),如果新值正在釋放或者是新值得類不支持weak,則會存儲一個nil代替.
首先會根據(jù)要存儲的對象去獲取SideTable,先分析一下SideTable的代碼
struct SideTable {?
?spinlock_t slock;?
?RefcountMap refcnts;?
//存儲weak_entry的hashTable
?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.?
static void lockTwo(SideTable *lock1, SideTable *lock2);?
? static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
可以看到SideTable內(nèi)部有一個成員變量weak_table,不難猜出這個weak_table是用來存儲weak_entry的,接下來繼續(xù)看weak_table的結(jié)構(gòu)
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_table_t就是一個內(nèi)部包含一個weak_entries指針的結(jié)構(gòu)體,繼續(xù)看weak_entry_t的結(jié)構(gòu)
/**
* The internal structure stored in the weak references table.
* It maintains and stores
* a hash set of weak references pointing to an object.
* If out_of_line_ness != REFERRERS_OUT_OF_LINE then the set
* is instead a small inline array.
*/
struct weak_entry_t {?
//weak變量指向的對象
?DisguisedPtr referent;
//選擇是用鏈表結(jié)構(gòu)還是一個小數(shù)組存儲所有指向該對象的弱引用指針,weak_referrer_t是弱引用指針的內(nèi)存地址
? ? 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用來存儲一個對象的weak引用的信息.weak_entry_t會保存一個HashSet,這個HashSet會存儲一個對象的弱引用的指針
所以現(xiàn)在可以得到結(jié)論了,weak變量初始化后保存在weak_entry_t這個結(jié)構(gòu)體中,weak_entry_t會存儲weak指向的對象,同時weak_entry_t 的referrers會存儲這個對象的所有weak指針,weak_entry_t會儲存在weak_table_t這個結(jié)構(gòu)體的weak_entries成員中,weak_table這個結(jié)構(gòu)也是一個標(biāo)準(zhǔn)的hashTab的實現(xiàn).weak_entry_t存儲在weak_table事,是以weak_entry_t的referent(即weak指針指向的對象)的hash值為key,weak_entry為value存儲在weak_table中的.
然后就是weak變量如何被釋放掉,在NSObject.mm文件中我可以找到如下函數(shù):
/** * Destroys the relationship between a weak pointer?
and the object it is referencing in the internal weak ?table. If the weak pointer is not referencing anything, there is no need to edit the weak table. ? This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.)?
void objc_destroyWeak(id *location){?
?(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating> ?(location, nil);
}
在注釋中寫的就是銷毀weak變量指針和對象的聯(lián)系,這個方法仍舊調(diào)用了stroreWeak,不過傳入的參數(shù)可以看到模板傳入的是DoHaveOld, DontHaveNew, DontCrashIfDeallocating ,實參傳入的是location(指針的內(nèi)存地址), nil,這時我們在回頭看storeWeak方法,我們可以看到這種情況下storeWeak會調(diào)用weak_unregister_no_lock方法,我們繼續(xù)看weak_unregister_no_lock方法,在objc-weak.mm中我們可以看到weak_unregister_no_lock的實現(xiàn):
**
* Unregister an already-registered weak reference.
* 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 The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
? ? ? ? ? ? ? ? ? ? ? ? id *referrer_id)
{
//weak變量指向的對象
? ? objc_object *referent = (objc_object *)referent_id;
//weak變量的指針地址
? ? objc_object **referrer = (objc_object **)referrer_id;
? ? weak_entry_t *entry;
? ? if (!referent) return;
//通過weak_entry_t的referent找到weak_entry然后移除對應(yīng)的referrer,
? ? if ((entry = weak_entry_for_referent(weak_table, referent))) {
? ? ? ? remove_referrer(entry, referrer);
? ? ? ? bool empty = true;
//判斷該對象的所有weak指針是否為空
? ? ? ? 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;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
//如果該對象的所有weak指針為空,則代表沒有任何weak指針指向這個對象,從weak_table中移除掉這個entry
? ? ? ? if (empty) {
? ? ? ? ? ? weak_entry_remove(weak_table, entry);
? ? ? ? }
? ? }
? ? // Do not set *referrer = nil. objc_storeWeak() requires that the
? ? // value not change.
}
從注釋中我們可以得到這個方法就是用來注銷已經(jīng)存在的若引用指針,這個方法調(diào)用的時機(jī)是weak變量的referrer(weak變量的指針)將要消失的時候.
從代碼的實現(xiàn)中我們可以看出,方法內(nèi)部首先移除的是weak_entry的referrer,如果weak_entry的referrers為空,再從weak_table中移除weak_entry.
首先我們要看remove_referrer這個方法:
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
//weak_entry->out_of_line()標(biāo)記weak_entry->referrers的結(jié)構(gòu)是數(shù)組還是hashTable
? ? if (! entry->out_of_line()) {
? ? ? ? for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
//如果找到weak_entry的referrer,就將其置nil
? ? ? ? ? ? if (entry->inline_referrers[i] == old_referrer) {
? ? ? ? ? ? ? ? entry->inline_referrers[i] = nil;
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? _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;
? ? }
? ? size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
? ? size_t index = begin;
? ? size_t hash_displacement = 0;
? ? while (entry->referrers[index] != old_referrer) {
? ? ? ? index = (index+1) & entry->mask;
? ? ? ? if (index == begin) bad_weak_table(entry);
? ? ? ? hash_displacement++;
? ? ? ? if (hash_displacement > entry->max_hash_displacement) {
? ? ? ? ? ? _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;
? ? ? ? }
? ? }
//找到weak變量的指針,將其置nil
? ? entry->referrers[index] = nil;
? ? entry->num_refs--;
}
從其代碼實現(xiàn),我們就可以很明白的理解為什么weak變量釋放時weak指針會自動置nil了
接下來我們繼續(xù)看weak_entry_remove方法:
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
? ? // remove entry
? ? if (entry->out_of_line()) free(entry->referrers);
? ? bzero(entry, sizeof(*entry));
? ? weak_table->num_entries--;
//根據(jù)weak_table的長度動態(tài)調(diào)整weak_table
? ? weak_compact_maybe(weak_table);
}
看注釋發(fā)現(xiàn),這個方法就是把weak_entry從weak_table中移除.看代碼的實現(xiàn),它釋放了weak_entry的referrent,同時將整個weak_entry內(nèi)存全部置為0,之后將weak_table的長度減一.
以上就完成了將weak指針和其對象的關(guān)聯(lián)銷毀的全部過程.
當(dāng)然還有一種情況就是weak指針指向的對象釋放后,是如何處理的.釋放對象的基本流程在NSObject.mm文件中也可以看到:
調(diào)用objc_release
若對象的引用計數(shù)為0泽腮,執(zhí)行dealloc
在dealloc中烘嘱,調(diào)用了_objc_rootDealloc函數(shù)
在_objc_rootDealloc中次舌,調(diào)用了object_dispose函數(shù)
調(diào)用objc_destructInstance
最后調(diào)用objc_clear_deallocating
我們只需關(guān)心objc_clear_deallocating這個方法:
inline void
objc_object::clearDeallocating()
{
? ? if (slowpath(!isa.nonpointer)) {
? ? ? ? // Slow path for raw pointer isa.
? ? ? ? sidetable_clearDeallocating();
? ? }
? ? 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());
}
從其實現(xiàn)代碼可以看到,針對被weak指向的對象,會調(diào)用clearDeallocating_slow方法,接下來可以看clearDeallocating_slow其實現(xiàn)代碼:
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
? ? assert(isa.nonpointer? &&? (isa.weakly_referenced || isa.has_sidetable_rc));
? ? SideTable& table = SideTables()[this];
? ? table.lock();
? ? if (isa.weakly_referenced) {
? ? ? ? weak_clear_no_lock(&table.weak_table, (id)this);
? ? }
? ? if (isa.has_sidetable_rc) {
? ? ? ? table.refcnts.erase(this);
? ? }
? ? table.unlock();
}
在實現(xiàn)中我們可以看到當(dāng)對象是若引用的時候會調(diào)用weak_clear_no_lock方法,所以我們繼續(xù)看weak_clear_no_lock的實現(xiàn),在objc-weak.mm中可以發(fā)現(xiàn)如下代碼,這個方法提供了在dealloc時,一次性釋放所有的weak變量
/**
* 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)
{
? ? objc_object *referent = (objc_object *)referent_id;
//從weak_table中找到weak_entry
? ? weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
? ? if (entry == nil) {
? ? ? ? /// XXX shouldn't happen, but does with mismatched CF/objc
? ? ? ? //printf("XXX no entry for clear deallocating %p\n", referent);
? ? ? ? return;
? ? }
? ? // zero out references
? ? weak_referrer_t *referrers;
? ? size_t count;
//判斷weak_entry的referrers的結(jié)構(gòu)
? ? if (entry->out_of_line()) {
? ? ? ? referrers = entry->referrers;
? ? ? ? count = TABLE_SIZE(entry);
? ? }
? ? else {
? ? ? ? referrers = entry->inline_referrers;
? ? ? ? count = WEAK_INLINE_COUNT;
? ? }
//釋放weak_entry的?referrers,將其置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();
? ? ? ? ? ? }
? ? ? ? }
? ? }
//從
? ? weak_entry_remove(weak_table, entry);
}
從注釋中可以看出,這個方法在dealloc的時候會被調(diào)用,當(dāng)weak指針指向的對象被釋放的時候,會將改對象的所有weak指針全部置nil,并將該對象的weak_entry從weak_table中移除.