上篇文章介紹了內存管理方案中的Tagged Pointer 小對象類型
沧侥,這篇文章來介紹下另一種方案sideTable 散列表
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);
};
由上述代碼得知散列表
其實就是個結構體,我們發(fā)現有refcnts和weak_table
這兩張表
引用計數
retain源碼分析
進入objc_retain -> retain -> rootRetain
源碼實現
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
//需要對引用計數+1鸽照,即retain+1与境,而引用計數存儲在isa的bits中兰珍,需要進行新舊isa的替換,所以這里需要isa
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判斷是否為nonpointer isa
if (slowpath(!newisa.nonpointer)) {
//如果不是 nonpointer isa洞拨,直接操作散列表sidetable
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
//是否正在析構
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//執(zhí)行引用計數+1操作剧劝,即對bits中的 1ULL<<45(arm64) 即extra_rc雁芙,用于該對象存儲引用計數值
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//判斷extra_rc是否滿了,carry是標識符
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
//如果extra_rc滿了熬荆,則直接將滿狀態(tài)的一半拿出來存到extra_rc
newisa.extra_rc = RC_HALF;
//給一個標識符為YES舟山,表示需要存儲到散列表
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//將另一半存在散列表中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
- 判斷是否是
Nonpointer_isa
,如果不是直接往散列表
中存 - 判斷
是否正在釋放
,如果正在釋放累盗,執(zhí)行dealloc
流程 - 執(zhí)行
extra_rc+1
寒矿,即引用計數+1操作,并給一個引用計數的狀態(tài)標識carry
若债,用于表示extra_rc是否滿了 - 如果是
carray
狀態(tài)符相,表示extra_rc
已經存滿,這時需要忘散列表
中存拆座。即將滿狀態(tài)的計數
一半存入extra_rc
主巍,一半存入散列表
冠息。這樣做是因為都存入散列表中
挪凑,每次對散列表操作都需要開解鎖,操作耗時逛艰,消耗性能大躏碳,這么對半分操作的目的在于提高性能
release源碼分析
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判斷是否是Nonpointer isa
if (slowpath(!newisa.nonpointer)) {
//如果不是,則直接操作散列表-1
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
//進行引用計數-1操作散怖,即extra_rc-1
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
//如果此時extra_rc的值為0了菇绵,則走到underflow
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//散列表中是否存儲了一半的引用計數
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
//從散列表中取出存儲的一半引用計數
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
//進行-1操作,然后存儲到extra_rc中
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//此時extra_rc中值為0镇眷,散列表中也是空的咬最,則直接進行析構,即自動觸發(fā)dealloc流程
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
//發(fā)送一個dealloc消息
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- 判斷是否是
Nonpointer isa
欠动,如果不是永乌,則直接對散列表進行-1操作 - 對
extra_rc
中的引用計數值進行-1操作,并存儲此時的extra_rc
狀態(tài)到carry中 - 如果此時的狀態(tài)
carray
為0具伍,則走到underflow
流程 - 判斷
散列表
中是否存儲了一半的
引用計數` - 如果是翅雏,則從散列表中取出存儲的一半引用計數,進行-1操作人芽,然后存儲到
extra_rc
中 - 如果此時extra_rc沒有值望几,散列表中也是空的,則直接進行析構
弱引用分析
先來看一個案例
NSObject *shObjc = [[NSObject alloc] init];
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(shObjc)),shObjc,&shObjc);
__weak typeof(id) weakObj = shObjc;
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(shObjc)),shObjc,&shObjc);
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(weakObj)),weakObj,&weakObj);
image.png
接下來分析下這個案例
- 第一個
NSLog
,引用計數
等于1
這里沒有問題 - 第二個
NSLog
,引用計數
等于1
這里也沒有問題 - 第三個
NSLog
,引用計數
等于2
這里就有問題了萤厅。我們知道weak
修飾的變量橄抹,是弱引用
,引用計數
是不增加的惕味,那么這里為什么等于2
呢楼誓?
接下來分析下源碼,在weak
聲明處下一個斷點
image.png
在
匯編
處發(fā)現了objc_initWeak
方法image.png
查看源碼赦拘,進入
objc_initWeak
方法慌随,在這里發(fā)現不論是初始化weak
,置空
還是銷毀
方法,都調用了storeWeak
方法,所以這個方法是個高度封裝的方法
image.png
進入storeWeak
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.
// 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.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(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 ? 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);
// This must be called without the locks held, as it can invoke
// arbitrary code. In particular, even if _setWeaklyReferenced
// is not implemented, resolveInstanceMethod: may be, and may
// call back into the weak reference machinery.
callSetWeaklyReferenced((id)newObj);
return (id)newObj;
}
- 這里有兩個入參
location (聲明的weakObj的指針地址)
和newObj (需要綁定的對象)
- 如果
haveOld
存在阁猜,則對弱引用表進行
移出丸逸,說明haveOld
代表的是移出操作。如果haveNew
存在,則進行對象注冊進弱引用表
image.png
進入weak_register_no_lock
方法
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;
// ensure that the referenced object is viable
// 如果正在析構剃袍,進行析構處理
if (deallocatingOptions == ReturnNilIfDeallocating ||
deallocatingOptions == CrashIfDeallocating) {
bool deallocating;
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
// Use lookUpImpOrForward so we can avoid the assert in
// class_getInstanceMethod, since we intentionally make this
// callout with the lock held.
auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
referent->getIsa());
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
}
if (deallocating) {
if (deallocatingOptions == 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
//entry 是個數組黄刚,里面存儲對象
weak_entry_t *entry;
// 如果entry 中有這個對象,將這個弱引用指針加入到對象中
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 否則創(chuàng)建一個新的entry,將對象加入entry民效,并將弱引用指針添加到對象中
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
// 將entry添加到弱引用表中
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
- 這里首先將傳入的參數轉換成
objc_object
的對象和弱引用指針
- 如果是
小對象類型
直接return
- 接下來判斷是否正在
析構
- 判斷
entry
是否有傳入的對象
- 如果有則將這個
弱引用指針
加入到對象中 - 如果沒有憔维,則創(chuàng)建一個
entry
,并將對象加入到entry
中,弱引用指針加入到對象
中
查看append_referrer
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.`
// 將 對象放入entry 中空閑的節(jié)點
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return;
}
}
// 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());
// 對 entry 進行擴容
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != nil) {
hash_displacement++;
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;
}
//
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
- 在
entry
默認是開辟了4個
空間 - 遍歷
ntry->inline_referrers
是否有空閑的畏邢,如果有直接將new_referrer
放入 - 如果沒有則要對
entry
進行擴容
WX20210914-111843@2x.png
由上圖得知弱引用表的結構為业扒,且都是1對多的關系
散列表
->弱引用表
->entry
-> 對象
->弱引用指針
- 通過
SideTable
得到weakTable
- 判斷
weakTable
是否有該對象的weak_entry_t
- 如果沒有,創(chuàng)建
weak_entry_t
- 把
referrer 弱引用指針地址
加入到weak_entry_t 數組中
inline_referrers ` - 如果
weak_entry_t
空間不夠舒萎,則進行擴容 - 并把
new_entry
加入到weak_table
中
回到剛開始介紹弱引用表的案例程储,并在第三個NSLog
處打一個斷點
image.png
在匯編代碼中發(fā)現了
objc_loadWeakRetained
這個方法image.png
進入objc_loadWeakRetained
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;
if (obj->isTaggedPointerOrNil()) return obj;
table = &SideTables()[obj];
table->lock();
if (*location != obj) {
table->unlock();
goto retry;
}
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());
if (! obj->rootTryRetain()) {
result = nil;
}
}
else {
// Slow case. We must check for +initialize and call it outside
// the lock if necessary in order to avoid deadlocks.
// Use lookUpImpOrForward so we can avoid the assert in
// class_getInstanceMethod, since we intentionally make this
// callout with the lock held.
if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
if ((IMP)tryRetain == _objc_msgForward) {
result = nil;
}
else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
result = nil;
}
}
else {
table->unlock();
class_initialize(cls, obj);
goto retry;
}
}
table->unlock();
return result;
}
- 傳入的
location
是我們的weakObj
-
obj = *location;
是weakObj 指向的內存
,也就是shObjc
我們在這打個斷點臂寝,并打印obj
的引用計數章鲤,這個時候為1
image.png
在經過
rootTryRetain
方法后引用計數就變?yōu)榱?code>2image.png
進入rootTryRetain
,發(fā)現這里進入了rootRetain
,這里我們在引用計數
時已經分析了
image.png
- 也就是說
弱引用對象
也會造成引用計數的增加 - 但是這里和我們平時所掌握的
weak
修飾的對象咆贬,引用計數不會增加败徊,有出入
接下來我們打印多次weakObj
,發(fā)現引用計數始終為2
掏缎,這里并沒有增加
image.png
是因為objc_loadWeakRetained
調用這個方法時,result
是一個臨時變量皱蹦,該方法最終也是return result
,這也就導致當去打印weakObj
的引用計數為2
,但是當離開作用域后,就會去釋放引用計數
id
objc_loadWeakRetained(id *location)
{
id obj;
id result;
Class cls;
SideTable *table;
retry:
// fixme std::atomic this load
obj = *location;
...
table = &SideTables()[obj];
...
result = obj;
...
return result;
}
再來看一個案例
__weak typeof(id) weakSelfObj ;
{
NSObject *objc = [[NSObject alloc] init];
weakSelfObj = objc;
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(objc)),objc,&objc);
weakSelfObj = objc;
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(objc)),objc,&objc);
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(weakSelfObj)),weakSelfObj,&weakSelfObj);
}
NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(weakSelfObj)),weakSelfObj,&weakSelfObj);
運行后發(fā)現崩潰了
image.png
是因為在作用域內部時御毅,weakSelfObj 和objc
指向同一片內存
image.png
出了作用域
weakSelfObj
指向的內存已經被釋放根欧,因為這里weakSelfObj
是弱持有指向的內存
,objc
是強持有內存端蛆,但是objc
是在作用域內部聲明凤粗,所以出了作用域指向的內存會被釋放
,所以這里也就崩潰了image.png