記憶體管理-弱引用分析
散列表結構分析
``` bool objc_object::sidetable_tryRetain() {
if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
endif
SideTable& table = SideTables()[this];
bool result = true;
auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
auto &refcnt = it.first->second;
if (it.second) {
// there was no entry
} else if (refcnt & SIDE_TABLE_DEALLOCATING) {
result = false;
} else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
refcnt += SIDE_TABLE_RC_ONE;
}
return result;
} ```
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
``` class StripedMap {
if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
else
enum { StripeCount = 64 };
endif
``
在 [記憶體管理-retain&realese](http://juejin.cn/post/7006566638833106951)
rootRetain函式中我們介紹了當
isa不是
nonpointer型別的時候就會直接操作散列表,對引用計數進行增加。我們進到
sidetable_tryRetain函式可以看到
SideTables,代表有很多張表,
SideTables在底層是
StripedMap,通過
StripedMap的結構可以看到,最大可以開 64 張表,如果是真機環境下最大就是 8 張表。而
StripedMap` 是對雜湊表的上層封裝。
``` 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);
}; ```
通過檢視 SideTable
的結構可以看到 SideTable
中包含了引用計數表跟弱引用表,而上面講的 SideTables
是多張表的形式就是考慮到效能問題,當所有物件都共用一張表的話因為要考慮到多執行緒的問題,當對引用計數操作的時候就會對錶的加鎖和關鎖,會比較消耗效能,當使用多張表的時候,系統可以根據一定的演算法,對不使用的表進行記憶體回收,而不是持續佔用空間。但是也不能每個物件開一張表,因為開表的記憶體太大了,物件很多的話就會有很多的記憶體開闢與回收,也會很消耗效能。所以表的數量要在一個合理的範圍內。
``` id objc_object::sidetable_retain(bool locked) {
if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
endif
SideTable& table = SideTables()[this];
if (!locked) table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
} ```
sidetable_retain
函式中,SideTables()[this]
根據 this
獲取到當前物件所在的表,size_t& refcntStorage = table.refcnts[this]
根據 this
找到物件在 table
中的儲存空間,然後對 refcntStorage
進行加操作。
弱引用表分析
如圖我們通過 __weak
對 weakObjc
進行修飾,我們在這裡進行斷點,通過彙編除錯可以看到來到了 objc_initWeak
函式,然後我們通過原始碼來看一下 objc_initWeak
函式做了哪些操作。
``` id objc_initWeak(id location, id newObj) { if (!newObj) { location = nil; return nil; }
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
} ```
```
// 這裡是 c++ 的模板函式
template
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry: // 這裡判斷物件是否存在舊的引用,如果第一次來到這裡說明沒加入到過弱引用表,就會走到 else 裡面 if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; }
// 這裡判斷是否存在新的引用,如果成立,就根據 newObjc 到 SideTables 中找到 newTable
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
// 如果沒有有新的弱引用,newTable 為 nil
newTable = nil;
}
// 這裡判斷物件是否存在舊的引用,如果存在就進行相關的移除.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 這裡判斷是新的引用
if (haveNew) {
// 這裡判斷物件是否是 objc_object 型別,是的話就呼叫 weak_register_no_lock 函式
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (!newObj->isTaggedPointerOrNil()) {
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);
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
} ```
``` id weak_register_no_lock(weak_table_t weak_table, id referent_id, id referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) { objc_object referent = (objc_object )referent_id; objc_object referrer = (objc_object )referrer_id;
if (referent->isTaggedPointerOrNil()) return referent_id;
weak_entry_t *entry;
// 這裡根據物件(referent)判斷 weak_table 是否存在 entry,如果存在就進行追加,往 entry 的 referrers 中新增 referent
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 如果 weak_table沒有對應的 entry,就通過 referent 跟 referrer 建立 new_entry
weak_entry_t new_entry(referent, referrer);
// 這裡進行記憶體的判斷,並根據規則進行擴容
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.
return referent_id;
} ```
總結如下:
* 首先根據物件在 SideTables
中查詢對應的 SideTable
* 判斷是否存在舊的引用,如果存在就進行相關的移除
* 判斷是新的引用且判斷物件是否是 objc_object
型別,是的話就呼叫 weak_register_no_lock
函式
* weak_register_no_lock
函式中根據物件(referent)
判斷 weak_table
是否存在 entry
,
* 如果存在就進行追加,往 entry
的 referrers
中新增 referent
- 不存在的話,就通過
referent
跟referrer
進行繫結,建立new_entry
,並且進行記憶體的判斷,並根據規則進行擴容,最後把新的new_entry
存入到weak_table
中
通過上圖可以看到,首先存在一個全域性的 SideTables
,然後 SideTables
中會有不同數量的 SideTable
,而且 SideTable
中存在引用計數表跟弱引用計數表 weak_table
,weak_table
又根據不同的物件對應不同的 weak_entry_t
,而 weak_entry_t
中包含 weak_referrer_t *referrers
,referrers
儲存的為 objc_object *
型別 DisguisedPtr<objc_object *> weak_referrer_t
。
關於 weak 引用計數的問題
如圖當我們列印 weakObjc
的引用計數的時候,發現等於 2,那麼這是什麼原因呢?我們通過斷點除錯來看一下。
通過彙編除錯我們可以看到列印 weakObjc
的時候呼叫了 objc_loadWeak
函式,下面我們就來追蹤 objc_loadWeak
函式。
id
objc_loadWeak(id *location)
{
if (!*location) return nil;
return objc_autorelease(objc_loadWeakRetained(location));
}
``` id objc_loadWeakRetained(id *location) { id obj; id result; Class cls;
SideTable *table;
retry: // 這裡 location 就是 weakObjc obj = location; if (obj->isTaggedPointerOrNil()) return obj;
// 根據 obj 獲取 table
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
// 這裡把 obj 賦值給臨時變數 result
result = obj;
cls = obj->ISA();
if (! cls->hasCustomRR()) {
// Fast case. We know +initialize is complete because
// default-RR can never be set before then.
ASSERT(cls->isInitialized());
// 這裡 rootTryRetain 會呼叫 rootRetain,所以引用計數會被加 1
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {...}
table->unlock();
return result;
// 出了這個作用域空間後 result 會被 release
} ```
ALWAYS_INLINE bool
objc_object::rootTryRetain()
{
return rootRetain(true, RRVariant::Fast) ? true : false;
}
在 objc_loadWeak
函式中我們可以看到,在這裡會呼叫 rootTryRetain
函式,然後 rootTryRetain
函式呼叫了 rootRetain
函式,然後就會走 rootRetain
流程,所以引用計數會被加 1,但是出了 objc_loadWeak
函式的時候,result
會被 release
,所以引用計數又會被減 1。
- Swift String、Moya 原始碼解析及高階函式
- Flutter 中 key 的原理及作用
- Flutter 生命週期及渲染原理
- Flutter 仿寫微信搜尋頁
- Flutter 網路請求類封裝及搜尋框實現
- Flutter 佈局聊天列表頁及網路資料處理
- Flutter 通訊錄索引條完善及聊天資料配置
- Flutter 仿寫微信通訊錄頁面
- Flutter 仿寫微信發現、我的頁面
- Flutter 專案搭建及工程配置
- 常用 Widget 部件介紹及 Flutter 佈局方式
- Flutter 之 Widget 部件體驗
- Dart 基礎語法
- 記憶體管理-弱引用分析
- NSTimer 不釋放問題分析及解決