文章目錄
- Retain、release復習
- SideTable
- spinlock_t slock 自旋鎖
- 鎖
- 分離鎖、拆分鎖
- 自旋鎖
- 蘋果的選擇
- RefcountMap
- weak_table_t weak_table
- 構造和解構式
- 最后是鎖的操作
- 小小總結一下SideTable
- weak部分
- objc_initWeak
- objc_storeWeak
- weak_register_no_lock將新的weak指標添加到弱參考表
- weak_entry_for_referent取元素
- append_referrer添加元素
- weak_unregister_no_lock移除舊weak指標地址
- dealloc部分
- 結論
Retain、release復習
我們在Strong實作部分了;了解過了retain和release的原始碼 先拿個圖扔這復習一下


release這里應該是


詳解見這個博客
[iOS開發]ARC
關于參考計數的存盤方式,清楚看來有兩種,一種是通過isa,另一種是通過SideTable
來詳細學習一下
SideTable
HashMap(哈希表)
基于陣列的一種資料結構,通過一定的演算法,把key進行運算得出一個數字,用這個數字做陣列下標,將value存入這個下標對應的記憶體之中
HashTon (哈希桶)
哈希演算法算出的數字有可能會重復,對于哈希值重復的資料,如何存入哈希表呢?常用方法有閉散列和開散列等方式,其中采用開散列方式的哈希表稱為哈希桶,開散列就是在哈希值對應的位置上,使用鏈表或陣列,將哈希值沖突的資料存入這個鏈表或者陣列中,提高查找效率
為了管理所有物件的參考計數和weak指標,蘋果創建了一個全域的SideTables,雖然名字后面又個"s",但其不過還是一個全域的Hash桶,里面的內容裝的都是SideTable結構體而已,它使用物件的記憶體地址當它的key,來管理參考計數和weak指標,
看一下SideTable的內部
struct SideTable {
spinlock_t slock; //自旋鎖
RefcountMap refcnts; //存放參考計數
weak_table_t weak_table; //weak_table是一個哈希
先學一下SideTable中的這三個成員變數
spinlock_t slock 自旋鎖
鎖
我們學習過了作業系統 鎖是執行緒同步時一個重要的工具
作業系統中有五大鎖
- 信號量:
-
- 整型信號量S,S<=0表示該資源已被占用,S>0表示該資源可用,pv操作進行訪問
-
- 記錄型信號量 s.value > 0 表示該資源可用的數目;< 0表示在等待鏈表中已經阻塞的數目
-
- AND型信號量,AND型信號量是指同時需要多個資源且每種占用一個資源時的信號量操作,
-
- 信號量集 對應有多種資源,相當于記錄型的集合
- 互斥量:和二元信號量類似,唯一不同的是,互斥量的獲取和釋放必須是在同一個執行緒中進行的,如果一個執行緒去釋放一個不是其所占有的信號量是無效的,而信號量是可以由其他執行緒釋放的,
- 臨界區:并發執行的行程中,訪問臨界資源的必須互斥執行的程式段叫臨界區
- 讀寫鎖:解決讀者寫者問題產生的鎖
- 條件變數:條件變數相當于一種通知機制,多個執行緒可以設定等待該條件變數,而一旦另外的執行緒設定了該條件變數(相當于喚醒條件變數)后,多個等待的執行緒就可以繼續執行了,
分離鎖、拆分鎖
因為物件參考計數相關操作應該是原子性的,不然如果多個執行緒同時去寫一個物件的參考計數,那就會造成資料錯亂,失去了記憶體管理的意義,同時又因為記憶體中物件的數量是很大的,需要非常頻繁的操作SideTables,所以不能對整個Hash表加鎖,蘋果采用了分離鎖技術
- 分拆鎖 (lock splitting) 和分離鎖 (lock striping) 是降低執行緒請求鎖的頻率從而達到降低鎖競爭的兩種方式,相互獨立的狀態變數,應該使用獨立的鎖進行保護,但有時開發者會錯誤的使用一個鎖保護所有的狀態變數,對于這些鎖需要仔細分配,以降低發生死鎖的風險
- 如果一個鎖守護多個相互獨立的狀態變數,你可能能夠通過分拆鎖,使每一個鎖守護不同的變數,這樣可以使每一個鎖被請求的頻率都變小了,分拆鎖對于中等競爭強度的鎖,能夠有效的把它們大部分轉化為非競爭的鎖,使性能和可可伸縮性都得到了提高,
- 分拆鎖有時候可以被擴展,分成若干加鎖塊的集合,并且它們歸屬于相互獨立的物件,這種情況就是分離鎖,
我們將每個SideTable里的每個物件的參考計數都加一把鎖,這就是分拆鎖,雖然安全 但是消耗很大
我們給每個SideTable加上一把鎖,只讓某個SideTable不能多次訪問,這就是分離鎖
自旋鎖
自旋鎖和互斥鎖
- 相同點:都能保證同一時間只有一個執行緒訪問共享資源,都能保證執行緒安全,
- 不同點:
-
- 互斥鎖:如果共享資料已經有其他執行緒加鎖了,執行緒會進入休眠狀態等待鎖,一旦被訪問的資源被解鎖,則等待資源的執行緒會被喚醒,
-
- 自旋鎖:如果共享資料已經有其他執行緒加鎖了,執行緒會以死回圈的方式等待鎖,一旦被訪問的資源被解鎖,則等待資源的執行緒會立即執行,
- 自旋鎖的效率高于互斥鎖,但是我們要注意由于自旋時不釋放CPU,因而持有自旋鎖的執行緒應該盡快釋放自旋鎖,否則等待該自旋鎖的執行緒會一直在哪里自旋,這就會浪費CPU時間,
- 在操作參考計數的時候對SideTable加鎖,避免資料錯誤
蘋果的選擇
對于每個SideTable,中間都有自旋鎖
同樣也使用了分離鎖給單個的SideTable上鎖
安全+效率很合理
RefcountMap
來了解一下這個圖

以DisguisedPtr<objc_object>為key的hash表,用來存盤OC物件的參考計數
不知道DisguisedPtr<objc_object>是什么,但是我們已經對retain中存盤參考計數的方式十分清晰了,如果未開啟isa優化 或 在isa優化情況下isa_t的extra_rc參考計數加一后向上溢位了,才會存入這個哈希表中,
weak_table_t weak_table
儲存物件弱參考指標的hash表,weak功能實作的核心資料結構,
看一下wewak_table_t
struct weak_table_t {
weak_entry_t *weak_entries; //連續地址空間的頭指標, 陣列
//管理所有指向某物件的weak指標,也是一個hash
size_t num_entries; //陣列中已占用位置的個數
uintptr_t mask; //陣列下標最大值(即陣列大小 -1)
uintptr_t max_hash_displacement; //最大哈希偏移值
};
weak_table_t中并沒有直接通過陣列存放weak指標,而是通過結構體來存放weak指標
兩個引數
- location:__weak指標的地址,存盤指標的地址,這樣便可以再最后將其指向的物件置nil
- newObj: 所參考的物件
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //被指物件的地址,前面回圈遍歷查找的時候就是判斷目標地址是否和他相等,
union {
struct {
weak_referrer_t *referrers; //可變陣列,里面保存著所有指向這個物件的弱參考的地址,當這個物件被釋放的時候,referrers里的所有指標都會被設定成nil,
//哈希的目的是清除一個weak指標
//指向 referent 物件的 weak 指標陣列
uintptr_t out_of_line_ness : 2; //這里標記是否超過行內邊界, 下面會提到
uintptr_t num_refs : PTR_MINUS_2; //陣列中已占用的大小
uintptr_t mask; //陣列下標最大值(陣列大小 - 1)
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]; //只有4個元素的陣列,默認情況下用它來存盤弱參考的指標,當大于4個的時候使用referrers來存盤指標,
//當指向這個物件的weak指標不超過4個,則直接使用陣列inline_referrers,省去hhash
};
};
union共用體 也是提醒我們蘋果是使用同一段記憶體去存放不同的資訊
中間有兩個陣列
weak_referrer_t *referrers和weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
在weak指標個數小于4的時候會存入第二個陣列,省去了hash,提高了存盤效率,大于4的時候才會存入referrers當中
構造和解構式
// 建構式
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
//解構式(看看函式體,蘋果設計的SideTable其實不希望被析構,不然會引起fatal 錯誤)
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
所以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

weak部分
簡單申請一個__weak修飾符修飾的變數 我們查看一下匯編

說到底只有兩個部分
我們看一下對應的原始碼部分
objc_initWeak
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
查看物件實體是否有效 無效物件直接導致指標的釋放
如果有效那么就會呼叫objc_storeWeak()函式
objc_storeWeak
看一下store函式上面的注釋
(直接放漢語版的了)
更新弱變數
如果haveOld為true,則變數有舊值,舊值需要被清理,這個值可能是nil[該weak指標之前已經有了指向]
如果haveNew為true,則有一個新值需要被分配到變數,這個值可能是nil
如果CrashIfDeallocating為true,且如果newObj正在解除分配或newObj的類不支持弱參考時,行程將停止
如果CrashIfDeallocating為false,則儲存nil
再來看一下其中的具體邏輯與判斷
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
// 該程序用來更新弱參考指標的指向
// 初始化previouslyInitializedClass指標
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// 模版函式,haveOld和haveNew由編譯器決定傳入的值,location是weak指標,newObj是weak指標將要指向的物件
retry:
if (haveOld) {
// 更改指標,獲得oldObj 為索引所儲存的值地址
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
// 更改新值指標,獲得以newObj為索引所儲存的值地址
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 加鎖操作,防止多執行緒中競爭沖突
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
// 避免執行緒沖突重處理
// location應該與oldObj保持一致,如果不同,說明當前的location已經處理過oldObj 可是又被其他執行緒所修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// 防止弱參考間死鎖
// 并且通過+initialize初始化構造器保證所有弱參考的isa非空指向
if (haveNew && newObj) {
//獲得新物件的isa指標
Class cls = newObj->getIsa();
// 判斷isa非空且已經初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//對其isa指標進行初始化
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
//如果該類已經完成執行+initialize方法是最理想情況
//如果該類+initialize在執行緒中
//例如+initialize正在呼叫storeWeak方法
//需要手動對其增加保護策略,并設定previouslyInitializedClass指標進行標記
previouslyInitializedClass = cls;
//重新嘗試
goto retry;
}
}
// Clean up old value, if any.
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
//如果weak指標將要指向新值,在weak_table中處理賦值操作
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
//如果弱參考被釋放的weak_register_no_lock方法回傳nil
//在參考計數表中設定若參考標記位
if (newObj && !newObj->isTaggedPointer()) {
//弱參考位初始化操作
//方法修改weak新參考的物件的bit標志位
newObj->setWeaklyReferenced_nolock();
}
// 之前不要設定location物件, 這里需要更改指標指向
*location = (id)newObj;
}
else {
// 沒有新值,則無需修改
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
storeWeak通過接受了3個引數haveOld、haveNew和crashIfDeallocation,這三個引數是以模版函式的方式傳入的,-
- haveOld:weak指標之前是否指向了一個弱參考
-
- haveNew:weak指標是否需要指向一個新的參考
-
- 如果被弱參考的物件正在析構,此時再弱參考該物件是否應該crash
-
- 初始化的時候傳入的值應該是0 1 1
- 同時其維護了兩張表
oldTable和newTable兩張表分別表示舊的弱參考表和新的弱參考表,他們都是SideTable的hash表 - 如果weak指標之前指向了一個弱參考,則會呼叫
weak_unregister_no_lock方法將舊的weak指標地址移除 - 如果weak指標需要指向一個新的參考
-
- 則會呼叫
weak_register_no_lock方法將新的weak指標地址添加到弱參考表中
- 則會呼叫
-
- 如果弱參考被釋放的weak_register_no_lock方法回傳nil,在參考計數表中設定弱參考標記位
weak_register_no_lock將新的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;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
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);
}
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 有對應的 entry
if ((entry = weak_entry_for_referent(weak_table, referent))) {//回傳給定參考物件的弱參考表項,如果referent沒有條目,則回傳NULL,執行查找,
append_referrer(entry, referrer); //將 weak 指標存入對應的 entry 中,官方翻譯:將給定的參考添加到此條目中的弱指標集,不執行重復檢查(不會將b/c弱指標添加到集合中兩次),
}
else {
weak_entry_t new_entry(referent, referrer); //創建新的 entry
weak_grow_maybe(weak_table); //查看是否需要調整 weak_table 中 weak_entries 陣列大小
weak_entry_insert(weak_table, &new_entry); //將新的 entry 插入到 weak_table 中,官方:將新的_項添加到物件的弱參考表中,不檢查參考物件是否已在表中,
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
- 傳入了4個引數
-
weak_table:weak_table_t結構體型別 我們之前已經有了解
-
referent_id:weak指標指向的物件
-
*referrer_id: weak指標的地址,操作時需要用到這個指標的地址
-
crashIfDeallocating:如果被弱參考的物件正在析構,此時再弱參考該物件是否應該crash
- 主要流程
- 如果referent(就是weak指標)為nil或referent采用了TaggedPointer計數方式,直接回傳,不做任何操作
- 如果正在析構,拋出例外
- 如果物件不能被weak參考,直接回傳nil
- 如果物件不屬于上面的情況,則呼叫
weak_entry_for_referent方法,根據弱參考物件的地址從弱參考表中找到對應的weak_entry(指向其物件的指標),如果能夠找到則呼叫append_referrer方法向其中插入weak指標地址,否則就新建一個weak_entry
核心有兩個對應的函式
weak_entry_for_referent取元素
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask; // 這里通過 & weak_table->mask的位操作,來確保index不會越界
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries); // 觸發bad weak table crash
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) { // 當hash沖突超過了可能的max hash 沖突時,說明元素沒有在hash表中,回傳nil
return nil;
}
}
return &weak_table->weak_entries[index];
}
append_referrer添加元素
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) { // 如果weak_entry 尚未使用動態陣列,走這里
// Try to insert inline.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 如果inline_referrers的位置已經存滿了,則要轉型為referrers,做動態陣列,
// Couldn't insert inline. Allocate out of line.
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[I];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
// 對于動態陣列的附加處理:
assert(entry->out_of_line()); // 斷言: 此時一定使用的動態陣列
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { // 如果動態陣列中元素個數大于或等于陣列位置總空間的3/4,則擴展陣列空間為當前長度的一倍
return grow_refs_and_insert(entry, new_referrer); // 擴容,并插入
}
// 如果不需要擴容,直接插入到weak_entry中
// 注意,weak_entry是一個哈希表,key:w_hash_pointer(new_referrer) value: new_referrer
// 細心的人可能注意到了,這里weak_entry_t 的hash演算法和 weak_table_t的hash演算法是一樣的,同時擴容/減容的演算法也是一樣的
size_t begin = w_hash_pointer(new_referrer) & (entry->mask); // '& (entry->mask)' 確保了 begin的位置只能大于或等于 陣列的長度
size_t index = begin; // 初始的hash index
size_t hash_displacement = 0; // 用于記錄hash沖突的次數,也就是hash再位移的次數
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask; // index + 1, 移到下一個位置,再試一次能否插入,(這里要考慮到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因為陣列每次都是*2增長,即8, 16, 32,對應動態陣列空間長度-1的mask,也就是前面的取值,)
if (index == begin) bad_weak_table(entry); // index == begin 意味著陣列繞了一圈都沒有找到合適位置,這時候一定是出了什么問題,
}
if (hash_displacement > entry->max_hash_displacement) { // 記錄最大的hash沖突次數, max_hash_displacement意味著: 我們嘗試至多max_hash_displacement次,肯定能夠找到object對應的hash位置
entry->max_hash_displacement = hash_displacement;
}
// 將ref存入hash陣列,同時,更新元素個數num_refs
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
- 首先確定是使用定長陣列還是動態陣列
- 定長陣列直接將weak指標地址添加到陣列
- 定長陣列已經用完,將定長陣列中的元素轉存到動態陣列中
weak_unregister_no_lock移除舊weak指標地址
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))) { // 查找到referent所對應的weak_entry_t
remove_referrer(entry, referrer); // 在referent所對應的weak_entry_t的hash陣列中,移除referrer
// 移除元素之后, 要檢查一下weak_entry_t的hash陣列是否已經空了
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;
}
}
}
if (empty) { // 如果weak_entry_t的hash陣列已經空了,則需要將weak_entry_t從weak_table中移除
weak_entry_remove(weak_table, entry);
}
}
- 首先它會在weak_table中找出referent對應的weak_entry_t
- 在weak_entry_t中移除referrer
- 移除元素后,判斷此時weak_entry_t中是否還有元素(empty == true)
- 如果此時weak_entry_t已經沒有元素了,則需要將weak_entry_t從weak_table中移除
dealloc部分
當物件的參考計數為0時,底層會呼叫_objc_rootDealloc方法對物件進行釋放,而在_objc_rootDealloc方法里面會呼叫rootDealloc方法,
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);
}
}
- 首先判斷物件是否是taggedPointer型別,如果是直接回傳
- 如果物件是采用了優化的isa計數方式,且同時滿足物件沒有被weak參考、沒有關聯物件、沒有自定義的C++析構方法、沒有用到SideTable來參考計數 則直接快速釋放
- 如果不能滿足2,則呼叫dispose方法
//dispose中主要呼叫了這個方法
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;
}
如果有自定義的C++析構方法,則呼叫解構式,如果有關聯物件,則移除關聯物件并將其自身從Association Manager的map中移除,呼叫clearDeallocation方法清楚物件的相關參考
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());
}
先判斷物件是否采用了優化isa參考計數,如果沒有的話則需要清理物件存盤在SideTable中的參考計數資料,
如果采用了優化isa參考計數,則判斷是否有使用SideTable的輔助參考計數(isa.has_sidetable_rc)或者有weak參考(isa.weakly_referenced),符合這兩種情況中一種的,呼叫clearDeallocating_slow 方法,
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this]; // 在全域的SideTables中,以this指標為key,找到對應的SideTable
table.lock();
if (isa.weakly_referenced) { // 如果obj被弱參考
weak_clear_no_lock(&table.weak_table, (id)this); // 在SideTable的weak_table中對this進行清理作業
}
if (isa.has_sidetable_rc) { // 如果采用了SideTable做參考計數
table.refcnts.erase(this); // 在SideTable的參考計數中移除this
}
table.unlock();
}

呼叫了weak_clear_no_lock方法來做weak_table的清理作業
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); // 找到referent在weak_table中對應的weak_entry_t
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參考referent的weak 指標地址陣列以及陣列長度
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]; // 取出每個weak ptr的地址
if (referrer) {
if (*referrer == referent) { // 如果weak ptr確實weak參考了referent,則將weak ptr設定為nil,這也就是為什么weak 指標會自動設定為nil的原因
*referrer = nil;
}
else if (*referrer) { // 如果所存盤的weak ptr沒有weak 參考referent,這可能是由于runtime代碼的邏輯錯誤引起的,報錯
_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); // 由于referent要被釋放了,因此referent的weak_entry_t也要移除出weak_table
}
結論
runtime維護了一個weak表,用于存盤指向某個物件的所有weak指標,weak表其實是一個weak_table_t結構的hash表,key是所指物件的地址,value是weak指標的地址陣列
weak實作的原理包括以下三步:
- 初始化時,runtime會對其使用
objc_initWeak函式,初始化一個新的weak指標指向物件的地址 - 添加參考時:
objc_initWeak函式會呼叫objc_storeWeak()函式,objc_storeWeak()的作用是用于更新指標指向,創建弱參考表, - 釋放時,呼叫
clearDeallocating函式,clearDeallocating函式首先根據物件地址獲取所有weak指標陣列,然后遍歷這個陣列,把其中的資料設為nil,最后把這個entry從weak表中洗掉,最后清理物件的記錄,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/291332.html
標籤:其他
上一篇:Flutter 與 Compose怎么選?小孩子才做選擇
下一篇:Android四大組件及變異測驗
