在項目中我們經常用到weak
指針,其可以保證在指向的對象釋放后,weak
指針自動置為nil
,以防止崩潰,因為在OC
中向nil
發送消息是沒有任何處理的。通過__weak
、property weak
等形式,都可以將指針修飾為weak
類型的。
weak
的實現原理其實很簡單,概括來說就是,在內存中有一個名為weak_table_t
的哈希表,weak_table_t
中存儲著App
所有的weak
對象及指針。當有對象被weak
指針修飾時,會將被修飾的對象及指針添加到weak_table_t
表中。當被weak
指針的作用域消失時,weak
指針會被銷毀,隨后會 從哈希表中查找對應的weak
指針,并將指針置為nil
。
weak引用表
SideTables
在weak
的實現中很多地方都用到了SideTables
函數,此函數內部直接返回了一個哈希表,哈希表名為SideTablesMap
。此函數用來保存整個程序所有被weak
指針指向的對象,和應用程序是一對一的。每個對象對應其中的一個元素,例如兩個weak
指針指向一個對象,則這個對象對應一個SideTable
對象,這個對象中保存這兩個weak
指針。
按照比較新的runtime 779.1
版本,對于SideTables
的定義如下。SideTables
本質上是一個靜態函數,其內部是通過SideTablesMap
實現的。
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
下面定義是一個嵌套模型,其中SideTablesMap
是一個objc
命名空間下的ExplicitInit
類,其內部實現的上面的get
函數。StripedMap
也是一個模型,傳入的SideTable
就是全局的weak
表所在的結構體。
static objc::ExplicitInit<StripedMap<SideTable>> SideTablesMap;
ExplicitInit
ExplicitInit
是一個模板類,傳入的type
就是上面定義的StripedMap
。在get
函數中有reinterpret_cast
關鍵字,這個關鍵字類似于強制類型轉換,改變類型但存儲的數據不變。
template <typename Type>
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];
public:
template <typename... Ts>
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}
Type &get() {
return *reinterpret_cast<Type *>(_storage);
}
};
StripedMap
我們繼續來看StripedMap
模板的定義:
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR //如果是iphone設備Map的數量為8
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize); //T value 64字節對齊
};
PaddedT array[StripeCount];// 所有PaddedT struct 類型數據被存儲在array數組中(這里的PaddedT 帶入代碼中即 SideTable)。
static unsigned int indexForPointer(const void *p) {// index的hash算法,該方法以void *作為key 來獲取void *對應在StripedMap 中的位置
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
//蘋果為array數組寫了一些公共的存取數據的方法,主要是調用indexForPointer方法,使得外部傳入的對象地址指針直接hash到對應的array節點,這里主要是對&符號重寫,所以我們經常看到&SideTables()[obj]這種調用操作,實際上就是調用這個array[indexForPointer(p)].value從而獲取SideTable
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
// Shortcuts for StripedMaps of locks.
void lockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.lock();
}
}
void unlockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.unlock();
}
}
void forceResetAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.forceReset();
}
}
void defineLockOrder() {
for (unsigned int i = 1; i < StripeCount; i++) {
lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
}
}
void precedeLock(const void *newlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
}
void succeedLock(const void *oldlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(oldlock, &array[0].value);
}
const void *getLock(int i) {
if (i < StripeCount) return &array[i].value;
else return nil;
}
#if DEBUG
StripedMap() {
// Verify alignment expectations.
uintptr_t base = (uintptr_t)&array[0].value;
uintptr_t delta = (uintptr_t)&array[1].value - base;
ASSERT(delta % CacheLineSize == 0);
ASSERT(base % CacheLineSize == 0);
}
#else
constexpr StripedMap() {}
#endif
};
SideTable
SideTable
結構體是weak
實現的核心,結構體中定義了引用計數表和弱引用表,弱引用使用weak_table
字段。下面的slock
自旋鎖,兩個表都會使用同一個鎖。
struct SideTable {
// 自旋鎖,保證線程安全
spinlock_t slock;
// 引用計數表,在未開啟isa指針優化,或isa指針存儲滿了才會用
RefcountMap refcnts;
// 弱引用表
weak_table_t weak_table;
};
weak_table_t
weak_table_t
是弱引用表,所有的弱引用都會被存儲在這個表中。在下面結構體變量的定義中,weak_entries
是一個哈希表的結構,其中key
是堆區內存地址,通過key
可以獲取weak_entries
鏈表中對應的weak_entry_t
,也就是指針數組。
struct weak_table_t {
// 弱引用數組,用來存儲weak_entry_t對象,是一個鏈表結構
weak_entry_t *weak_entries;
// 弱引用數組大小,如果到閾值會自動擴容
size_t num_entries;
// 進行哈希運算的mask,大小是num_entries-1
uintptr_t mask;
// 最大沖突數,一般不會大于這個數
uintptr_t max_hash_displacement;
};
weak_entry_t
weak_entry_t
結構體內部的定義是一個union
聯合體,聯合體中包含兩個結構體,此結構體用來存儲指針地址。在存儲時會判斷,如果同一個對象的weak
指針數量少于4
,則使用inline_referrers
定長數組,否則使用referrers
動態長度數組。
有趣的是,因為是union
聯合體,當weak
指針數量大于4
之后,不需要另外開辟空間,在當前空間直接覆蓋inline_referrers
的存儲區域,使用referrers
哈希表。
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
// 弱引用該對象的,指針地址的哈希數組
weak_referrer_t *referrers;
// 是否使用動態長度數組
uintptr_t out_of_line_ness : 2;
// referrers數組中的元素
uintptr_t num_refs : PTR_MINUS_2;
// 參與哈希運算的值,是referrers數組分配的長度,會隨著動態擴容而改變
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};
weak_referrer_t
無論是inline_referrers
數組還是referrers
鏈表,都是一個weak_referrer_t
的定義,在這個typedef
中定義了一個指向objc_object
的指針,isa
指針都是被優化為objc_object
的,所以這個指針指向這個被優化的isa
。也可以簡單理解為,weak_referrer_t
本質上存儲著被weak
修飾的指針地址。
結合weak_entry_t
的定義,可以知道referrers
數組存儲結構,就是直接將DisguisedPtr
轉換后的整型負數,存儲在referrers
數組中。
typedef DisguisedPtr<objc_object *> weak_referrer_t;
DisguisedPtr
DisguisedPtr
是一個模板工具類,主要起到將指針和整型相互轉換的作用,目的是為了隱藏指針,起到一個偽裝的作用。隱藏指針的作用在于,一方面是為了安全性,另一方面也防止leaks
這些檢測工具的誤判。
template <typename T>
class DisguisedPtr {
// 存儲地址轉換為整數的結果
uintptr_t value;
// 將地址轉為整數,并取反
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
// 將轉換為負數的地址,先取反隨后轉換為地址
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
public:
// 構造函數
DisguisedPtr() { }
// 通過指針,初始化value
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr<T>& ptr)
: value(ptr.value) { }
// 運算符重載,將指針轉換為DisguisedPtr的過程,直接由“&”取地址符來實現
DisguisedPtr<T>& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr<T>& operator = (const DisguisedPtr<T>& rhs) {
value = rhs.value;
return *this;
}
operator T* () const {
return undisguise(value);
}
// 運算符重載,將地址轉為指針
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[i];
}
};
創建weak指針
objc_initWeak
通過__weak
等形式創建的指針,編譯器會將其轉換為objc_initWeak
函數的調用,并將被指向對象及weak
指針傳進去。但需要注意的是,此函數并不是線程并發安全的,所以需要注意多線程的使用。
id objc_initWeak(id *location, id newObj) {
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
在調用函數后會將haveNew
、newObj
等參數傳入,供storeWeak
調用。
storeWeak
無論是創建weak
指針還是銷毀weak
指針,其內部實現都是通過storeWeak
函數實現的。storeWeak
函數是一個C++
的模板函數,函數會傳入五個參數。其中haveOld
和haveNew
是互斥的,haveNew
則表示新創建的weak
指針,haveOld
則表示將已有的weak
指針置為nil
,或將指針重定向。
由于storeWeak
函數中的代碼量較大,所以只保留了核心代碼,不重要的代碼都刪掉了,看的也清楚點。如果是通過objc_initWeak
調用進來,下面template
的三個bool
參數定義,分別如下。
-
haveOld
=false
-
haveNew
=true
-
crashIfDeallocating
=true
template <HaveOld haveOld, HaveNew haveNew,
CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
retry:
// 判斷location是否已經指向weak對象,有的話先把舊對象取出來
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
// 將新傳入的對象,對應的weak表取出來,如果這個對象之前被weak指針指向過,則返回有值
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
// 如果有舊對象,但是不是location所指向的,也就是當前傳入的對象。則表示從retry執行到這里,可能被其他線程改過,所以需要重新執行retry
if (haveOld && *location != oldObj) {
goto retry;
}
// 有新對象,則判斷類是否初始化,沒有初始化則先初始化,再重新執行retry
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
_class_initialize(_class_getNonMetaClass(cls, (id)newObj));
previouslyInitializedClass = cls;
goto retry;
}
}
// 如果location指針指向過其他對象,則執行weak_unregister_no_lock,將weak表和舊對象解綁
if (haveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// 將傳入的新對象,以及新對象的指針地址,添加到weak表中
if (haveNew) {
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
}
return (id)newObj;
}
在storeWeak
函數中會進行判斷,如果是haveNew
則表示創建一個weak
指針,這時會判斷被指向的對象是否第一次傳入,如果是則會創建新的內存空間,如果是第二次被weak
指向則取出之前已經創建的weak_table
。當釋放weak
指針時也是如此,先取出SideTable
對象再從weak_table
中移除對應的weak
指針。
隨后會進入一個判斷,會判斷弱引用對象所屬的類對象是否未執行+initialized
方法,如果未執行則調用下面的代碼初始化類對象。以保證在弱引用對象和+initialized
方法之間,不會產生死鎖。
最后會進入具體的實現中,這些實現代碼都在objc-weak.mm
中。移除weak
引用或指針重定向會通過weak_unregister_no_lock
函數實現,添加新的weak
引用則會通過weak_register_no_lock
函數實現。
weak_register_no_lock
weak_register_no_lock
函數是添加weak
引用的關鍵,在函數中會有是否可以進行weak
操作的判斷,如果不符合條件則return
或拋出異常。需要注意的是,在NSObject.mm
文件中提供了allowsWeakReference
方法,可以通過此方法返回是否允許使用weak
引用,在下面的代碼中也會向此方法發送消息來做確認。
weak_register_no_lock
函數傳入的四個參數如下。
-
weak_table
,全局的weak
哈希表 -
referent_id
,weak
指針。 -
*referrer_id
,weak
指針地址。 -
crashIfDeallocating
,如果weak
指向的地址正在釋放中,是否crash
。
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;
// 判斷傳入的對象是否為空,以及是否tagged pointer這些邊界判斷
if (!referent || referent->isTaggedPointer()) return referent_id;
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
// 有自定義的retain實現,執行自定義方法
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("error");
} else {
return nil;
}
}
// 判斷堆區內存是否有被weak引用過,如果有的話就加入到已有的weak_entry_t中
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 如果沒有,則創建一個weak_entry_t,并插入到全局的weak_table哈希表中
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
}
return referent_id;
}
在函數中通過referent
獲取到被weak
引用的對象,通過referrer
獲取到指針對象,隨后會將這兩個變量包裝成weak_entry_t
結構體,并調用對應的函數添加到哈希表中。
執行添加操作的有兩個關鍵函數,當對象第一次有weak
指針指向時,會調用weak_entry_insert
將weak_entry_t
插入到哈希表中,后面的weak
指向都會調用append_referrer
函數向表中添加referrer
。
weak_entry_for_referent
weak_entry_for_referent
函數用來查找referent
,weak
指向的堆區地址,也就是objc_object
對象的地址。隨后通過hash_pointer
函數找到referent
所在的index
,也就是哈希表的key
。在while
循環中遍歷weak_entries
表,從index
開始查找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;
// 通過哈希算法獲取到referent的index,也就是所在的下標
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 循環時從index所在的下標開始,這樣查找速度會很快
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
append_referrer
此函數是對weak_entry_t
進行插入,向數組后面拼接指向指針的地址。內部會根據哈希表大小,決定用動態數組還是定長數組,并且會根據使用情況,對動態數組進行擴容。并最終插入到weak_entry_t
的末尾。
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
// 如果還未開啟動態長度數組,則進入這里
if (! entry->out_of_line()) {
// 先嘗試定長數組,如果有空位置就插入
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 如果定長數組存滿了,就創建動態數組,并將定長數組的對象插入進來
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
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());
// 判斷weak_entry_t已經使用的大小,是否超過了已經開辟的3/4,如果超過則進行動態擴容
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
// 通過哈希算法查找當前的index
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
// 通過while循環,找到referrers數組的最新一個為空的位置
while (entry->referrers[index] != nil) {
hash_displacement++;
// 由于擴容是成倍擴容的,所以mask的值一定是,0x111, 0x1111, 0x11111
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
// 將new_referrer插入到新的空位置,并將count加一
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
weak_entry_insert
在weak_table_t
中定義了weak_entries
結構體變量,這是一個數組結構,弱引用對象都保存在這里。當插入一個weak_entry_t
時會遍歷數組,并找到合適的位置將其插入。
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
指針重定向
有時候會把weak
指針指向一個新的對象,這時候會涉及到指針重定向。新建weak
指針會傳入newObj
并調用對應的函數,weak
指針作用域銷毀會傳入oldObj
并調用對應的函數,而指針重定向則這兩個參數都有值,haveNew
和haveOld
也都是有值的。
指針重定向會在一次storeWeak
函數調用中處理,并按順序先調用weak_unregister_no_lock
函數,先將weak
指針從哈希表中移除,再調用weak_register_no_lock
函數添加到新的SideTable
中。
dealloc
rootDealloc
當對象釋放時,會調用dealloc
方法,并調用到rootDealloc
來實現釋放邏輯。在釋放時會判斷,如果沒有弱引用等邏輯,可以直接調用free
函數釋放,否則調用object_dispose
函數處理釋放邏輯。
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return;
// 如果符合下面條件,則直接調用free函數釋放內存
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);
}
}
objc_destructInstance
object_dispose
函數中調用的objc_destructInstance
函數,在objc_destructInstance
函數中,執行了一些釋放和收尾的工作。而weak
的釋放操作,就在clearDeallocating
中完成的。
void *objc_destructInstance(id obj)
{
if (obj) {
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
clearDeallocating
在clearDeallocating
函數中,會判斷是否開啟指針優化,如果未開啟則執行sidetable
的釋放邏輯。如果開啟了指針優化,并且有weak
指針,或引用計數的邏輯,則執行clearDeallocating_slow
函數。
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
clearDeallocating_slow();
}
assert(!sidetable_present());
}
在clearDeallocating_slow
函數內部,實際上通過weak_clear_no_lock
函數對weak
指針釋放邏輯進行的實現。
weak_clear_no_lock
weak
最核心的功能,即對象釋放時,將weak
指針指向nil
。這個邏輯就在weak_clear_no_lock
函數中實現的。
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
// 根據被釋放的對象referent_id,從weak_table中找到weak_entry_t結構體
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
return;
}
weak_referrer_t *referrers;
size_t count;
// 判斷用的是定長數組,還是動態數組,并將數組賦值給referrers指針
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
// 遍歷weak數組,并將指向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_table哈希表中,將weak_entry_t移除
weak_entry_remove(weak_table, entry);
}
釋放優化
objc_destroyWeak
當weak
作為一個局部變量出現時,編譯器會對weak
進行優化。創建對象后,會通過objc_initWeak
的方式將weak
指針添加到哈希表中。在作用域結束時,會通過objc_destroyWeak
將weak
直接釋放掉。
當weak
指針作用域消失時,系統會調用objc_destroyWeak
函數來處理,并在函數內部調用storeWeak
函數。隨后storeWeak
函數中會傳入haveOld
,表示是執行移除weak
的操作。
void objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
weak_unregister_no_lock
weak_unregister_no_lock
函數是在storeWeak
函數中調用的,當weak
指針第一次指向或重新指向時,都會調用storeWeak
函數。如果重新指向,會調用weak_unregister_no_lock
先將之前的移除掉。
函數會通過referent
獲取到被weak
引用的對象,通過referrer
獲取到指針對象。隨后會通過weak_entry_for_referent
函數查找被指向的對象是否有weak_entry_t
,如果找到則進入if
語句中。
在if
語句中會調用remove_referrer
函數,將被weak
指針從weak_entry_t
中移除,并將weak
指針置nil
,這步就是最關鍵的一步了。隨后會判斷,如果被指向的對象,所有weak
指針都沒有了,則將對象從weak_table
的哈希表中移除。
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;
// 從weak_table中獲取weak_entry_t對象,如果有的話則進入if語句
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 從weak_entry_t中刪除referrer引用
remove_referrer(entry, referrer);
// 判斷刪除后,weak_entry_t是否還有弱引用指針指向這塊內存地址
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;
}
}
}
// 如果為空,則將weak_entry_t從全局weak_table哈希表中刪除
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
}
remove_referrer
這是weak
實現的關鍵函數,和向weak_entry_t
中添加一樣,只是這個過程是逆向的。會先遍歷inline_referrers
數組中有沒有指針對象,如果有則將指針置為nil
,否則就查找referrers
數組。
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (!entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
}
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
entry->referrers[index] = nil;
entry->num_refs--;
}