對于學習來說渔隶,最大的成本不是金錢羔挡,而是時間。低質(zhì)量低效率的學習不僅是對金錢的浪費间唉,更是對時間绞灼、生命的浪費。
先來看一系列大廠必問的iOS的高階面試題:
- 什么是ARC&MRC?底層是如何實現(xiàn)的呈野?
- 對象調(diào)用alloc和init方法之后低矮,引用計數(shù)是0還是1?為什么被冒?
- weak實現(xiàn)原理以及weak指針是怎樣移除的军掂?何時移除?
然后帶著這三個問題開始本篇干貨:
- iOS內(nèi)存布局及優(yōu)化技巧
- 內(nèi)存管理機制ARC&MRC
- 內(nèi)存管理之引用計數(shù)retain/release底層實現(xiàn)
- 內(nèi)存管理之dealloc底層實現(xiàn)
- 內(nèi)存管理之weak底層實現(xiàn)
一昨悼、內(nèi)存布局及優(yōu)化
三圖看懂內(nèi)存布局及優(yōu)化
1.內(nèi)存布局及存儲類型
2.內(nèi)存布局方向的優(yōu)化技巧
二蝗锥、內(nèi)存管理機制ARC&MRC
1、內(nèi)存管理機制:
引用計數(shù)機制率触,創(chuàng)建時引用計數(shù)為1终议,被持有會對引用計數(shù)+1,對象不再使用或者手動release會對引用計數(shù)-1葱蝗,當引用計數(shù)為0的時候由系統(tǒng)進行銷毀穴张。(注意釋放的條件,不要和release混淆两曼,release只是引用計數(shù)-1皂甘,而不是釋放)
引用計數(shù)管理機制:誰創(chuàng)建,誰釋放悼凑;誰引用叮贩,誰管理击狮。
2、MRC和ARC的異同:
MRC(全稱Manual Reference Counting 手動引用計數(shù))和ARC(全稱Automatic Reference Counting益老,自動引用計數(shù)彪蓬,iOS5推出)底層都是引用計數(shù)機制。
ARC是編譯屬性捺萌,是編譯器和runtime結(jié)合(對象的持有和釋放)實現(xiàn)的結(jié)果档冬。
三、內(nèi)存管理之引用計數(shù)retain/release底層實現(xiàn)
打開Objc源碼桃纯,在objc-object.h
中找到這倆的實現(xiàn)
retain的實現(xiàn):
// Equivalent to calling [this retain], with shortcuts if there is no override
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return sidetable_retain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
release的實現(xiàn)部分:
// Equivalent to calling [this release], with shortcuts if there is no override
inline void
objc_object::release()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
sidetable_release();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
明顯看出底層分別調(diào)用了sidetable_retain()
和sidetable_release()
酷誓。
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
//根據(jù)對象的地址,從一大堆散列表中态坦,獲取當前對象引用計數(shù)的散列表
SideTable& table = SideTables()[this];
//自旋鎖,加鎖
table.lock();
//獲取引用計數(shù)
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//引用計數(shù)增加
refcntStorage += SIDE_TABLE_RC_ONE;
}
//自旋鎖,解鎖
table.unlock();
return (id)this;
}
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
//根據(jù)對象的地址盐数,從大散列表中,獲取當前對象引用計數(shù)的散列表
SideTable& table = SideTables()[this];
//定義局部變量伞梯,是否需要dealloc
bool do_dealloc = false;
//自旋鎖加鎖
table.lock();
//獲取當前對象的引用計數(shù)
RefcountMap::iterator it = table.refcnts.find(this);
//判斷是否需要dealloc
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
//如果不需要dealloc玫氢,則引用計數(shù)減1
it->second -= SIDE_TABLE_RC_ONE;
}
//自旋鎖解鎖
table.unlock();
//如果需要dealloc則發(fā)送消息,調(diào)用SEL_dealloc
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
存儲SideTable
的全局哈希映射表StripedMap
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
散列表SideTable
的結(jié)構(gòu):
struct SideTable {
spinlock_t slock;//內(nèi)核自旋鎖spinlock_t
RefcountMap refcnts;//引用計數(shù)字典map
weak_table_t weak_table;//weak表
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(); }
//提供給weak操作的地址順序鎖
// 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);
};
思考:為什么Runtime會映射維護多張散列表SideTable谜诫?而不是維護一張散列表?
回答:由于散列表中的操作會加鎖(比如引用計數(shù)增加漾峡、減少都會加自旋鎖),如果只有一個表的話喻旷,假如我有兩個類Person生逸,Student,那么我對Person對象進行操作的時候且预,再想對Student對象進行操作槽袄,就只能等待Person的操作完成解鎖后才能操作,這樣效率會大大下降锋谐。并且多次對同一張表進行操作遍尺,提高了使用頻率,可能會造成稍微有修改就需要動整張表的不夠合理的操作怀估。
而使用了多張表狮鸭,就避免了以上的幾種問題,哪個類需要處理就去改對應(yīng)的表多搀,而且避開了鎖的問題歧蕉,效率上也提升了很高,用空間換時間康铭。
分析源碼總結(jié)retain和release的實現(xiàn)邏輯如下:
Runtime
維護了一個全局哈希映射表StripedMap
惯退,根據(jù)對象可以在全局映射表中可以獲取該對象對應(yīng)的散列表SideTable
,該SideTable
擁有三個成員變量从藤,一個自旋鎖spinlock_t
催跪,一個引用計數(shù)表RefcountMap
锁蠕,以及一個weak表weak_table_t
。引用計數(shù)表RefcountMap
以對象的地址作為key懊蒸,以引用計數(shù)作為value荣倾。
retain
是objc_object
在底層調(diào)用了sidetable_retain()
,查找引用計數(shù)表骑丸,做了引用計數(shù)refcntStorage+=SIDE_TABLE_RC_ONE;
的操作舌仍;
release
是objc_object
在底層調(diào)用了sidetable_release()
,查找引用計數(shù)表通危,做了引用計數(shù)refcntStorage-=SIDE_TABLE_RC_ONE;
的操作铸豁;如果引用計數(shù)小于閥值SIDE_TABLE_DEALLOCATING
,就調(diào)用SEL_dealloc
圖文總結(jié)如下:
由于SideTable
結(jié)構(gòu)體中包含一個自旋鎖菊碟,筆者此處拓展一下自旋鎖相關(guān)知識:互斥鎖的作用节芥,以及和自旋鎖的區(qū)別
自旋鎖是互斥鎖的一種實現(xiàn),互斥鎖的作用就是確保同一時間只有一個線程訪問數(shù)據(jù)逆害,對資源加鎖后头镊,會等待資源解鎖,在這期間會阻塞線程忍燥,直到解鎖拧晕;而自旋鎖則是
忙等
隙姿,在加鎖后梅垄,會不斷地去詢問判斷是否解鎖。兩者的區(qū)別主要是:在等待期間互斥鎖會放棄CPU输玷,而自旋鎖會不斷的循環(huán)測試鎖的狀態(tài)队丝,會一直占用CPU。
總結(jié)之后欲鹏,來看一個retainCount經(jīng)典的面試題:
對象進行alloc和init之后的引用計數(shù)值為多少机久?到底是0還是1?為什么赔嚎?
帶著問題膘盖,我們源碼中全局搜索retainCount {
找到實現(xiàn),并進入rootRetainCount
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
rootRetainCount
源碼尤误,注意代碼邏輯
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
//散列表SideTable提供的自旋鎖侠畔,加鎖
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//判斷bits.nonpointer
if (bits.nonpointer) {
//這里先直接+1
uintptr_t rc = 1 + bits.extra_rc;
//判斷有沒有使用sideTable存儲引用計數(shù),如果有损晤,就加上
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
//散列表自旋鎖解鎖
sidetable_unlock();
//沒有就直接返回软棺,
return rc;
}
//散列表SideTable提供的自旋鎖,解鎖
sidetable_unlock();
//直接返回sidetable_retainCount
return sidetable_retainCount();
}
下面是sidetable_retainCount的實現(xiàn)
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
//設(shè)置初始值1
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
打上斷點尤勋,對象創(chuàng)建走的是bits.nonpointer
為1的邏輯喘落,此時bits.extra_rc
存儲的引用計數(shù)為0茵宪,但是引用計數(shù)rc = 1 + bits.extra_rc
,所以返回了1
從這個源碼中我們又看出一件事瘦棋,引用計數(shù)不一定存儲在哈希表中稀火,還有可能存儲在類isa的
bits.extra_rc
中,找到isa的結(jié)構(gòu)isa_t
以及isa_t
的結(jié)構(gòu)中找到宏定義ISA_BITFIELD
筆者把宏定義整理了一下赌朋,注意其中的
has_sidetable_rc
和extra_rc
:
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
uintptr_t nonpointer : 1; // 0:普通指針憾股,1:優(yōu)化過,使用位域存儲更多信息
uintptr_t has_assoc : 1; // 對象是否含有或曾經(jīng)含有關(guān)聯(lián)引用
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc
uintptr_t shiftcls : 33; // 存放著 Class箕慧、Meta-Class 對象的內(nèi)存地址信息
uintptr_t magic : 6; // 用于在調(diào)試時分辨對象是否未完成初始化
uintptr_t weakly_referenced : 1; // 是否被弱引用指向
uintptr_t deallocating : 1; // 對象是否正在釋放
uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲引用計數(shù)
uintptr_t extra_rc : 19; // 引用計數(shù)能夠用 19 個二進制位存儲時服球,直接存儲在這里
};
#endif
};
經(jīng)過分析,isa_t
里面的extra_rc
也是用來存儲引用計數(shù)的颠焦,只不過大小有限斩熊,如果超過了大小,就會存儲到散列表SideTable中伐庭。
總結(jié)剛才的答案如下:
問題:對象進行alloc和init之后的引用計數(shù)值為多少粉渠?到底是0還是1?為什么圾另?
回答:對象alloc之后霸株,在引用計數(shù)表中的引用計數(shù)其實為0,只是在獲取retainCount的方法
rootRetainCount
的內(nèi)部進行了+1的操作
總結(jié)對象生命周期引用計數(shù)的變化圖如下:
四集乔、內(nèi)存管理之dealloc底層實現(xiàn)
直接進入源碼搜索dealloc {
去件,找到底層函數(shù)調(diào)用流程如下:
-
dealloc
底層調(diào)用_objc_rootDealloc()
-
_objc_rootDealloc()
調(diào)用objc_object::rootDealloc()
-
objc_object::rootDealloc()
調(diào)用object_dispose()
-
object_dispose()
進行了free(obj)
釋放對象,同時調(diào)用objc_destructInstance()
- 在
objc_destructInstance()
函數(shù)中判斷是否有析構(gòu)函數(shù)和關(guān)聯(lián)引用扰路,如果有侣肄,就要移除诉濒,最后調(diào)用clearDeallocating()
- 在
clearDeallocating()
中進行引用計數(shù)refcnt的清除和weak指針的移除兼贡,并調(diào)用weak_clear_no_lock()
(這個weak指針移除具體步驟在下面的weak指針清除的時候進行詳細分析份招。)
下面貼出上述步驟的所有源碼:
1、dealloc
底層調(diào)用_objc_rootDealloc()
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
2哩罪、_objc_rootDealloc()
調(diào)用objc_object::rootDealloc()
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
3授霸、objc_object::rootDealloc()
調(diào)用object_dispose()
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
4、object_dispose()
進行了對象的釋放free(obj)
际插,同時調(diào)用objc_destructInstance()
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
//釋放obj
free(obj);
return nil;
}
5碘耳、在objc_destructInstance()
函數(shù)中判斷是否有析構(gòu)函數(shù)和關(guān)聯(lián)引用,如果有腹鹉,就要移除藏畅,最后調(diào)用clearDeallocating()
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
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());
}
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
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);
}
table.unlock();
}
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();
}
總結(jié)一下dealloc主要做了下面這些內(nèi)容:
- 首先判斷并調(diào)用對象C++的析構(gòu)函數(shù),釋放對象占用的空間,順序是:先子類->后父類 -----
object_cxxDestruct()
- 然后清除對象關(guān)聯(lián)引用 -----
_object_remove_assocations()
- 然后從weak表中清空并移除對象對應(yīng)的所有weak指針 -----
weak_clear_no_lock()
- 然后移除引用計數(shù) -----
table.refcnts.erase(it);
- 最后釋放對象 free(obj)
對應(yīng)流程圖總結(jié)如下:
五愉阎、內(nèi)存管理之weak底層實現(xiàn)
分析完retain/release
以及dealloc
的底層實現(xiàn)绞蹦,我們來繼續(xù)分析weak
的底層實現(xiàn)及weak表的插入和移除操作:
當我們開發(fā)時用__weak
修飾變量,其實Runtime會在底層調(diào)用objc_initWeak
函數(shù)榜旦,objc_initWeak()
底層又調(diào)用了storeWeak()
幽七。storeWeak()
的作用就是向表中注冊弱引用指針,或者更新表溅呢,下面是storeWeak()
的實現(xiàn)部分澡屡,筆者把注釋寫在里面:
/**
* 注意注釋部分!8谰伞驶鹉!注釋已經(jīng)說的很直白了
* 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<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
//
// Acquire locks for old and new values.
獲取舊值和新值的鎖。
// Order by lock address to prevent lock ordering problems.
按鎖地址排序铣墨,以防止出現(xiàn)鎖排序問題室埋。
// Retry if the old value changes underneath us.
如果舊值在之后改變就重試操作
retry:
//獲取舊值和新值
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;
}
//
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
//防止初始化機制和弱引用機制之間出現(xiàn)死鎖
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.
//分配新值之前,清除舊值
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
// 分配新值
if (haveNew) {
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
// 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 = (id)newObj;
}
else {
//沒有新值就不改
// No new value. The storage is not changed.
}
//解鎖
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
然后我們來看一下SideTable
中weak表weak_table_t
的源碼:
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
//保存了所有指向指定對象的 weak 指針
weak_entry_t *weak_entries;
//存儲空間
size_t num_entries;
//引用計數(shù)輔助量
uintptr_t mask;
//最大偏移值
uintptr_t max_hash_displacement;
};
從之前引用計數(shù)的分析以及weak表的源碼中分析得出:Runtime維護了一個全局哈希映射表StripedMap
伊约,不同對象映射著對應(yīng)的散列表SideTable
姚淆,散列表中包含引用計數(shù)map以及weak弱引用表weak_table_t
,weak_table_t
中保存了所有指定對象的weak指針屡律,用對象的地址作為key腌逢,weak_entry_t
結(jié)構(gòu)體對象作為value;weak_entry_t
負責維護和存儲指向一個對象的所有弱引用的散列表超埋。
weak_entry_t
源碼:
/**
* 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.
*/
typedef DisguisedPtr<objc_object *> weak_referrer_t;
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;
}
}
};
weak表的存儲結(jié)構(gòu)思維導(dǎo)圖總結(jié)如下:
針對weak_table_t
還提供了三個主要的方法:向表中添加元素的方法weak_register_no_lock
搏讶、從表中移除元素的weak_unregister_no_lock
、以及清空weak指針weak_clear_no_lock
向添加weak_table_t
中添加方法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 The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
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;
//weak指針地址
objc_object **referrer = (objc_object **)referrer_id;
......
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//添加到表中
append_referrer(entry, referrer);
}
else {
//創(chuàng)建新的weak_entry_t實體
//根據(jù)對象及弱指針生成一個weak_entry_t結(jié)構(gòu)體對象纳本,并插入表中
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
//插入到表中
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
移除方法weak_unregister_no_lock
的源碼:
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;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//移除表中的weak指針
remove_referrer(entry, referrer);
......
if (empty) {
//移除weak_table表中對應(yīng)的的entry
weak_entry_remove(weak_table, entry);
}
}
}
清空對象的weak指針窍蓝,調(diào)用時機就是對象dealloc的時候:
/**
* 調(diào)用時機:
* 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
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;
}
//獲取weak_entry_t中的weak_referent_t腋颠,遍歷weak_referrer_t繁成,將其中的weak指針置為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);
}
總結(jié)一下:
1、weak
底層實現(xiàn)的流程
Runtime全局維護了一個全局映射表StripedMap淑玫,根據(jù)對象的地址能夠獲取對應(yīng)的散列表SideTable(注意=硗蟆!絮蒿!也有可能是多個對象共用一個散列表)尊搬,散列表SideTable之中包含有weak表
weak_table_t
,weak_table_t
中根據(jù)對象的地址能夠查到該對象對應(yīng)的weak_entry_t
實體土涝,weak_entry_t
用來管理對象的所有的weak指針佛寿,weak指針存儲在weak_referrer_t
中。當我們在用
__weak
修飾對象的時候,運行時Runtime會在底層調(diào)用objc_initWeak()
方法
objc_initWeak()
方法會調(diào)用storeWeak()
冀泻;
storeWeak()
這個函數(shù)會先判斷對象是否初始化常侣,如果未初始化,則進行對象初始化弹渔,然后創(chuàng)建對應(yīng)的SideTable胳施;如果對象已經(jīng)有SideTable,那么判斷weak指針是否需要更新肢专,更新操作就是刪除對應(yīng)location位置的weak_entry_t對象舞肆,創(chuàng)建新的weak_entry_t,然后插入到weak表weak_table_t中博杖。
2椿胯、weak指針移除原理
1、移除時機:調(diào)用對象的dealloc方法時剃根,中間會調(diào)用
clearDeallocating
压状,其中會調(diào)用weak_clear_no_lock
對weak指針進行移除。2跟继、移除原理:
weak_clear_no_lock
底層會獲取weak表weak_table_t
中的實體weak_entry_t
种冬,然后拿到其中的weak_referrer_t
,拿到weak_referrer_t
之后舔糖,遍歷并將其中的所有weak指針置為nil娱两,最后把這個weak_entry_t
從weak_table_t
中移除。3金吗、weak指針本質(zhì):從源碼中可以看出weak指針的類型為是
objc_object**
十兢,是對象的二維指針,就是指向?qū)ο蟮刂返闹羔槨?/strong>
接下來看一個問題:
如果在dealloc中使用__weak會有什么樣的結(jié)果摇庙?
答案:會crash!
回到源碼旱物,在storeWeak
函數(shù)源碼的上邊找到下面這部分代碼
// Update a weak variable.
// 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.
enum CrashIfDeallocating {
DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
以及注冊weak指針的方法
/**
* 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 The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
......
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;
}
}
......
return referent_id;
}
結(jié)合第一部分注釋部分以及第二部分的注冊函數(shù)weak_register_no_lock
進行分析:如果一個對象正在進行dealloc的時候,進行weak指針的更新操作卫袒,就會直接crash宵呛!并報錯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.
注冊時這么判斷作用就是:在對象正在釋放的過程中,或者對象已經(jīng)釋放后夕凝,是不允許使用weak來引用實例變量的宝穗。這樣就是為了防止野指針的出現(xiàn)。
-END-