本文首發於我的博客html
維基百科中這麼定義引用計數c++
引用計數是計算機編程語言中的一種內存管理技術,是指將資源(能夠是對象、內存或磁盤空間等等)的被引用次數保存起來,當被引用次數變爲零時就將其釋放的過程。使用引用計數技術能夠實現自動資源管理的目的。同時引用計數還能夠指使用引用計數技術回收未使用資源的垃圾回收算法。git
當建立一個對象的實例並在堆上申請內存時,對象的引用計數就爲1,在其餘對象中須要持有這個對象時,就須要把該對象的引用計數加1,須要釋放一個對象時,就將該對象的引用計數減1,直至對象的引用計數爲0,對象的內存會被馬上釋放。github
一個新建立的OC對象引用計數默認是1,當引用計數減爲0,OC對象就會銷燬,釋放其佔用的內存空間算法
調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1編程
內存管理的經驗總結bash
能夠經過如下私有函數來查看自動釋放池的狀況架構
extern void _objc_autoreleasePoolPrint(void)
;在詳解iOS中的Runtime一文中,對isa進行了詳解。app
這裏進行簡單概述編程語言
從arm64架構開始,蘋果對isa進行了優化,變成了一個共用體(union)結構,還使用位域來存儲更多的信息。以下
define ISA_BITFIELD \
uintptr_t nonpointer : 1; //指針是否優化過 \
uintptr_t has_assoc : 1; //是否有設置過關聯對象,若是沒有,釋放時會更快 \
uintptr_t has_cxx_dtor : 1; //是否有C++的析構函數(.cxx_destruct),若是沒有,釋放時會更快 \
uintptr_t shiftcls : 33; //存儲着Class、Meta-Class對象的內存地址信息 \
uintptr_t magic : 6; //用於在調試時分辨對象是否未完成初始化 \
uintptr_t weakly_referenced : 1; //是否有被弱引用指向過,若是沒有,釋放時會更快 \
uintptr_t deallocating : 1; //對象是否正在釋放 \
uintptr_t has_sidetable_rc : 1; //引用計數器是否過大沒法存儲在isa中 \
uintptr_t extra_rc : 19 //裏面存儲的值是引用計數器減1
複製代碼
nonpointer
has_assoc
has_cxx_dtor
shiftcls
magic
weakly_referenced
deallocating
extra_rc
has_sidetable_rc
再開始以前,先看這個代碼
NSNumber *num = @(20);
咱們只有一個須要存儲20這個數據,按照正常的技術方案,在64位CPU下,應該先去建立NSNumber對象,其值是20,而後再有個指向該地址的指針num
。這樣作存在什麼問題呢?
內存浪費
性能浪費
爲了解決這個問題,蘋果提出了Tagged Pointer的概念。對於 64 位程序,引入 Tagged Pointer 後,相關邏輯能減小一半的內存佔用,以及 3 倍的訪問速度提高,100 倍的建立、銷燬速度提高。
從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象的存儲
在沒有使用Tagged Pointer以前, NSNumber等對象須要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值
使用Tagged Pointer以後,NSNumber指針裏面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中
當指針不夠存儲數據時,纔會使用動態分配內存的方式來存儲數據
objc_msgSend
能識別Tagged Pointer,好比NSNumber的intValue方法,直接從指針提取數據,節省了之前的調用開銷
如何判斷一個指針是否爲Tagged Pointer?
關於Tagged Pointer,想深刻了解的,能夠參照深刻理解 Tagged Pointer,就不在這贅述了。須要注意的是,以前的版本,變量的值直接存儲在指針中,很容易的能夠讀取出來,例如0xb000000000000012
然而如今的版本中,蘋果對這個指針作了一些編碼處理,不能直接看出來是Tagged Pointer,例如0x30a972fb5e339e15
然而它依然是Tagged Pointer,由於能夠根據源碼可知,是根據把它轉爲二進制以後最後一位是否爲1來肯定是否爲Tagged Pointer。
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
複製代碼
在64bit中,引用計數能夠直接存儲在優化過的isa指針中,也可能存儲在SideTable類中,那SideTable
中有什麼呢?
SideTable
的結構以下
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;//refcnts是一個存放着對象引用計數的散列表
weak_table_t weak_table;
...還有不少代碼
};
複製代碼
其中 RefcountMap refcnts
中存放着對象引用計數的散列表
// 引用計數
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
複製代碼
rootRetainCount
inline uintptr_t
objc_object::rootRetainCount()
{
//TaggedPointer不是一個普通的對象,不須要作引用計數的一些操做
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { //優化過的isa
uintptr_t rc = 1 + bits.extra_rc; // 這裏進行了+1操做
if (bits.has_sidetable_rc) {
//能來到這裏,說明引用計數不是存儲在isa中,而是存儲在sidetable中
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();
}
複製代碼
sidetable_getExtraRC_nolock
size_t
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this]; // this 就是key 根據這個key取出value
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT; // 取出的值 通過位運算以後返回
}
複製代碼
sidetable_retainCount()
方法的邏輯就是先從 SideTable
的靜態方法獲取當前實例對應的 SideTable
對象,其 refcnts
屬性就是以前說的存儲引用計數的散列表,而後在引用計數表中用迭代器查找當前實例對應的鍵值對,獲取引用計數值,並在此基礎上 +1 並將結果返回。這也就是爲何以前中說引用計數表存儲的值爲實際引用計數減一。
須要注意的是爲何這裏把鍵值對的值作了向右移位操做(it->second >> SIDE_TABLE_RC_SHIFT)
在MRC 環境下可使用 retain 和 release 方法對引用計數進行加一減一操做,它們分別調用了_objc_rootRetain(id obj)
和 _objc_rootRelease(id obj)
函數,不事後二者在 ARC 環境下也可以使用。最後這兩個函數又會調用 objc_object 的下面兩個方法:
inline id
objc_object::rootRetain()
{
assert(!UseGC);
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
inline bool
objc_object::rootRelease()
{
assert(!UseGC);
if (isTaggedPointer()) return false;
return sidetable_release(true);
}
複製代碼
就是先看釋放支持isTaggedPointer,而後再操做 SideTable 中的 refcnts 屬性,這與獲取引用計數策略相似。sidetable_retain() 將 引用計數加一後返回對象,sidetable_release() 返回是否要執行 dealloc 方法:
引用計數的增長
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
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; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ 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; newisa.extra_rc = RC_HALF; 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; } 複製代碼
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
複製代碼
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;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);//引用計數減小
}
// don't check newisa.fast_rr; we already called any RR overrides uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- 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;
...還有不少代碼
複製代碼
sidetable_release
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
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)) { it->second -= SIDE_TABLE_RC_ONE; } table.unlock(); if (do_dealloc && performDealloc) {// 來到這裏,說明引用計數爲0,調用dealloc釋放 ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return do_dealloc; } 複製代碼
更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。