前言
在iOS開發(fā)過(guò)程中庆亡,會(huì)經(jīng)常使用到一個(gè)修飾詞“weak”,使用場(chǎng)景大家都比較清晰录煤,用于一些對(duì)象相互引用的時(shí)候鳄厌,避免出現(xiàn)強(qiáng)強(qiáng)引用,對(duì)象不能被釋放妈踊,出現(xiàn)內(nèi)存泄露的問(wèn)題了嚎。
weak 關(guān)鍵字的作用弱引用,所引用對(duì)象的計(jì)數(shù)器不會(huì)加一廊营,并在引用對(duì)象被釋放的時(shí)候自動(dòng)被設(shè)置為 nil歪泳。
weak底層原理
1.weak編譯解析
首先需要看一下weak編譯之后具體出現(xiàn)什么樣的變化,通過(guò)Clang的方法把weak編譯成C++
int main(){
NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}
編譯之后的weak露筒,通過(guò)objc_ownership(weak)實(shí)現(xiàn)weak方法呐伞,objc_ownership字面意思是:獲得對(duì)象的所有權(quán),是對(duì)對(duì)象weak的初始化的一個(gè)操作慎式。
在使用clang編譯過(guò)程中會(huì)報(bào)錯(cuò)誤荸哟,使用下方的方法編碼編譯出現(xiàn)error
clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m
int main(){
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_ownership(weak))) obj1 = obj;
}
2.weak的實(shí)現(xiàn)原理
第一、通過(guò)weak編譯解析瞬捕,可以看出來(lái)weak通過(guò)runtime初始化的并維護(hù)的;
第二舵抹、weak和strong都是Object-C的修飾詞肪虎,而strong是通過(guò)runtime維護(hù)的一個(gè)自動(dòng)計(jì)數(shù)表結(jié)構(gòu)。
綜上:weak是有Runtime維護(hù)的weak表惧蛹。
在runtime源碼中扇救,可以找到'objc-weak.h'和‘objc-weak.mm’文件刑枝,并且在objc-weak.h文件中關(guān)于定義weak表的結(jié)構(gòu)體以及相關(guān)的方法。
2.1.weak表
weak_table_t是一個(gè)全局weak 引用的表迅腔,使用不定類型對(duì)象的地址作為 key装畅,用 weak_entry_t 類型結(jié)構(gòu)體對(duì)象作為 value 。其中的 weak_entries 成員
/**
* 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; //保存了所有指向指定對(duì)象的weak指針 weak_entries的對(duì)象
size_t num_entries; // weak對(duì)象的存儲(chǔ)空間
uintptr_t mask; //參與判斷引用計(jì)數(shù)輔助量
uintptr_t max_hash_displacement; //hash key 最大偏移值
};
weak全局表中的存儲(chǔ)weak定義的對(duì)象的表結(jié)構(gòu)weak_entry_t沧烈,weak_entry_t是存儲(chǔ)在弱引用表中的一個(gè)內(nèi)部結(jié)構(gòu)體掠兄,它負(fù)責(zé)維護(hù)和存儲(chǔ)指向一個(gè)對(duì)象的所有弱引用hash表。其定義如下:
typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //范型
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
}
}
在 weak_entry_t 的結(jié)構(gòu)中锌雀,DisguisedPtr referent 是對(duì)泛型對(duì)象的指針做了一個(gè)封裝蚂夕,通過(guò)這個(gè)泛型類來(lái)解決內(nèi)存泄漏的問(wèn)題。從注釋中寫 out_of_line 成員為最低有效位腋逆,當(dāng)其為0的時(shí)候婿牍, weak_referrer_t 成員將擴(kuò)展為多行靜態(tài) hash table。其實(shí)其中的 weak_referrer_t 是二維 objc_object 的別名惩歉,通過(guò)一個(gè)二維指針地址偏移等脂,用下標(biāo)作為 hash 的 key,做成了一個(gè)弱引用散列撑蚌。
out_of_line:最低有效位上遥,也是標(biāo)志位。當(dāng)標(biāo)志位 0 時(shí)锨并,增加引用表指針緯度露该。
num_refs:引用數(shù)值。這里記錄弱引用表中引用有效數(shù)字第煮,因?yàn)槿跻帽硎褂玫氖庆o態(tài) hash 結(jié)構(gòu)解幼,所以需要使用變量來(lái)記錄數(shù)目。
mask:計(jì)數(shù)輔助量包警。
max_hash_displacement:hash 元素上限閥值撵摆。
其實(shí) out_of_line 的值通常情況下是等于零的,所以弱引用表總是一個(gè) objc_objective 指針二維數(shù)組害晦。一維 objc_objective 指針可構(gòu)成一張弱引用散列表特铝,通過(guò)第三緯度實(shí)現(xiàn)了多張散列表,并且表數(shù)量為 WEAK_INLINE_COUNT 壹瘟。
objc_object是weak_entry_t表中weak弱引用對(duì)象的范型對(duì)象的結(jié)構(gòu)體結(jié)構(gòu)鲫剿。
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*indexed=false*/);
void initClassIsa(Class cls /*indexed=maybe*/);
void initProtocolIsa(Class cls /*indexed=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasIndexedIsa();
bool isTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool indexed, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
bool overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Unified retain count manipulation for nonpointer isa
id rootRetain(bool tryRetain, bool handleOverflow);
bool rootRelease(bool performDealloc, bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
bool rootRelease_underflow(bool performDealloc);
void clearDeallocating_weak();
// Side table retain count overflow for nonpointer isa
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
bool sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable *table);
bool sidetable_release(bool performDealloc = true);
bool sidetable_release_slow(SideTable *table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if !NDEBUG
bool sidetable_present();
#endif
};
總之:
1.weak_table_t(weak 全局表):采用hash(哈希表)的方式把所有weak引用的對(duì)象,存儲(chǔ)所有引用weak對(duì)象
2.weak_entry_t(weak_table_t表中hash表的value值稻轨,weak對(duì)象體):用于記錄hash表中weak對(duì)象
3.objc_object(weak_entry_t對(duì)象中的范型對(duì)象灵莲,用于標(biāo)記對(duì)象weak對(duì)象):用于標(biāo)示weak引用的對(duì)象。
詳細(xì)講解weak存儲(chǔ)對(duì)象結(jié)構(gòu)殴俱,對(duì)接下來(lái)對(duì)weak操作使用可以更加清晰的理解weak的使用政冻。
2.2.weak底層實(shí)現(xiàn)原理
在runtime源碼中的NSObject.mm文件中找到了關(guān)于初始化和管理weak表的方法
- 初始化weak表方法
//初始化weak表
/**
* 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;
*
* @param addr Address of __weak ptr.
* @param val Object ptr.
*/
id objc_initWeak(id *addr, id val)
{
*addr = 0;
if (!val) return nil;
return objc_storeWeak(addr, val); // 存儲(chǔ)weak對(duì)象
}
- 存儲(chǔ)weak對(duì)象的方法
/**
* This function stores a new value into a __weak variable. It would
* be used anywhere a __weak variable is the target of an assignment.
*
* @param location The address of the weak pointer itself
* @param newObj The new object this weak ptr should now point to
*
* @return \e newObj
*/
id
objc_storeWeak(id *location, id newObj)
{
id oldObj;
SideTable *oldTable;
SideTable *newTable;
spinlock_t *lock1;
#if SIDE_TABLE_STRIPE > 1
spinlock_t *lock2;
#endif
// 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;
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);
lock1 = &newTable->slock;
#if SIDE_TABLE_STRIPE > 1
lock2 = &oldTable->slock;
if (lock1 > lock2) {
spinlock_t *temp = lock1;
lock1 = lock2;
lock2 = temp;
}
if (lock1 != lock2) spinlock_lock(lock2);
#endif
spinlock_lock(lock1);
if (*location != oldObj) {
spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) spinlock_unlock(lock2);
#endif
goto retry;
}
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = newObj;
spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
if (lock1 != lock2) spinlock_unlock(lock2);
#endif
return newObj;
}
- 舊對(duì)象解除注冊(cè)操作 weak_unregister_no_lock
該方法主要作用是將舊對(duì)象在 weak_table 中接觸 weak 指針的對(duì)應(yīng)綁定枚抵。根據(jù)函數(shù)名,稱之為解除注冊(cè)操作明场。從源碼中汽摹,可以知道其功能就是從 weak_table 中接觸 weak 指針的綁定。而其中的遍歷查詢苦锨,就是針對(duì)于 weak_entry 中的多張弱引用散列表逼泣。
- 新對(duì)象添加注冊(cè)操作 weak_register_no_lock
這一步與上一步相反,通過(guò) weak_register_no_lock 函數(shù)把心的對(duì)象進(jìn)行注冊(cè)操作逆屡,完成與對(duì)應(yīng)的弱引用表進(jìn)行綁定操作圾旨。
-
初始化弱引用對(duì)象流程一覽
弱引用的初始化,從上文的分析中可以看出魏蔗,主要的操作部分就在弱引用表的取鍵砍的、查詢散列、創(chuàng)建弱引用表等操作莺治,可以總結(jié)出如下的流程圖:
SideTable 這個(gè)結(jié)構(gòu)體廓鞠,是對(duì)weak_table_t表的再次封裝操作,避免對(duì)weak_table_t直接操作谣旁,SideTable使用更加方便床佳。
class SideTable {
private:
static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
public:
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
SideTable() : slock(SPINLOCK_INITIALIZER)
{
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable()
{
// never delete side_table in case other threads retain during exit
assert(0);
}
static SideTable *tableForPointer(const void *p)
{
# if SIDE_TABLE_STRIPE == 1
return (SideTable *)table_buf;
# else
uintptr_t a = (uintptr_t)p;
int index = ((a >> 4) ^ (a >> 9)) & (SIDE_TABLE_STRIPE - 1);
return (SideTable *)&table_buf[index * SIDE_TABLE_SIZE];
# endif
}
static void init() {
// use placement new instead of static ctor to avoid dtor at exit
for (int i = 0; i < SIDE_TABLE_STRIPE; i++) {
new (&table_buf[i * SIDE_TABLE_SIZE]) SideTable;
}
}
};
總之根據(jù)以上對(duì)weak進(jìn)行存儲(chǔ)的過(guò)程可以通過(guò)下邊的流程圖詳細(xì)的描述出來(lái)
3.weak釋放為nil過(guò)程
weak被釋放為nil,需要對(duì)對(duì)象整個(gè)釋放過(guò)程了解榄审,如下是對(duì)象釋放的整體流程:
1砌们、調(diào)用objc_release
2、因?yàn)閷?duì)象的引用計(jì)數(shù)為0搁进,所以執(zhí)行dealloc
3浪感、在dealloc中,調(diào)用了_objc_rootDealloc函數(shù)
4饼问、在_objc_rootDealloc中影兽,調(diào)用了object_dispose函數(shù)
5、調(diào)用objc_destructInstance
6莱革、最后調(diào)用objc_clear_deallocating峻堰。
對(duì)象準(zhǔn)備釋放時(shí),調(diào)用clearDeallocating函數(shù)盅视。clearDeallocating函數(shù)首先根據(jù)對(duì)象地址獲取所有weak指針地址的數(shù)組捐名,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個(gè)entry從weak表中刪除闹击,最后清理對(duì)象的記錄桐筏。
在對(duì)象被釋放的流程中,需要對(duì)objc_clear_deallocating方法進(jìn)行深入的分析
void objc_clear_deallocating(id obj)
{
assert(obj);
assert(!UseGC);
if (obj->isTaggedPointer()) return;
obj->clearDeallocating();
}
//執(zhí)行 clearDeallocating方法
inline void objc_object::clearDeallocating()
{
sidetable_clearDeallocating();
}
// 執(zhí)行sidetable_clearDeallocating,找到weak表中的value值
void objc_object::sidetable_clearDeallocating()
{
SideTable *table = SideTable::tableForPointer(this);
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
spinlock_lock(&table->slock);
RefcountMap::iterator it = table->refcnts.find(this);
if (it != table->refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
weak_clear_no_lock(&table->weak_table, (id)this);
}
table->refcnts.erase(it);
}
spinlock_unlock(&table->slock);
}
對(duì)weak置nil的操作最終調(diào)用執(zhí)行weak_clear_no_lock方法用于執(zhí)行置nil的操作梅忌。執(zhí)行方法如下:
/**
* 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_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;
if (entry->out_of_line) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
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);
}
objc_clear_deallocating該函數(shù)的動(dòng)作如下:
1、從weak表中獲取廢棄對(duì)象的地址為鍵值的記錄
2除破、將包含在記錄中的所有附有 weak修飾符變量的地址牧氮,賦值為nil
3、將weak表中該記錄刪除
4瑰枫、從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址為鍵值的記錄
其實(shí)Weak表是一個(gè)hash(哈希)表踱葛,然后里面的key是指向?qū)ο蟮牡刂罚琕alue是Weak指針的地址的數(shù)組光坝。
總結(jié)
weak是Runtime維護(hù)了一個(gè)hash(哈希)表尸诽,用于存儲(chǔ)指向某個(gè)對(duì)象的所有weak指針。weak表其實(shí)是一個(gè)hash(哈希)表盯另,Key是所指對(duì)象的地址性含,Value是weak指針的地址(這個(gè)地址的值是所指對(duì)象指針的地址)數(shù)組。