如今咱們使用oc編程不用進行手動內存管理得益於ARC機制。ARC幫咱們免去了大部分對對象的內存管理操做,其實ARC只是幫咱們在合適的地方或者時間對對象進行-retain
或-release
,並非不用進行內存管理。c++
經過我以前分析的oc對象內存結構能夠知道,其實對象的引用計數是存放在對象的isa
指針中,isa
在OBJC2
中是一個通過優化的指針不單存放着類對象的地址還存放着其餘有用的信息,其中就包括引用計數信息的存儲。 isa_t
的結構位域中有兩個成員與引用計數有關分別是編程
uintptr_t has_sidetable_rc : 1; //isa_t指針第56位開始佔1位
uintptr_t extra_rc : 8 //isa_t指針第57位開始佔8位
複製代碼
extra_rc
存放的是多是對象部分或所有引用計數值減1。數組
has_sidetable_rc
爲一個標誌位,值爲1時表明 extra_rc
的8位內存已經不能存放下對象的retainCount
, 須要把一部分retainCount
存放地另外的地方。安全
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
//isTaggedPointer直接返回指針
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
//標識是否須要去查找對應的SideTable
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//這裏是isa沒有通過指針位域優化的狀況,直接進入全局變量中找出對應的SideTable類型值操做retainCount
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
//是否溢出的標識 , 若是調用addc函數後 isa的extra_rc++後溢出的話carry會變成非零值
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed extra_rc 溢出了
if (!handleOverflow) {
ClearExclusive(&isa.bits);
//這裏是從新調用rootRetain參數handleOverflow = true
return rootRetain_overflow(tryRetain);
}
//執行到這裏表明extra_rc已經移除了,須要把 extra_rc 減半 ,把那一半存放到對應的SideTable類型值中
// 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;
}
複製代碼
rootRetain
主要是處理isa
中extra_rc
中加法操做: 在extra_rc ++
沒有溢出的狀況下不用特殊處理,若是溢出的話把extra_rc
一半的值減掉,把減掉的值存到一個SideTable
類型的變量中。bash
struct SideTable {
spinlock_t slock; //操做內部數據的鎖,保證線程安全
RefcountMap refcnts;//哈希表[假裝的對象指針 : 64位的retainCoint信息值]
weak_table_t weak_table;//存放對象弱引用指針的結構體
}
複製代碼
SideTabel
實際上是一個包裝了3個成員變量的結構體上面已註釋各成員的做用,而RefcountMap refcnts
這個成員就是咱們稍後重點要分析的存放對象額外retainCount
的成員變量。ide
獲取objc_object
對應的SideTable
類型變量函數
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
SideTable& table = SideTables()[this];
//函數SideTables() 實現
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
複製代碼
能夠看出全部對象對應的SideTable
。都存儲在一個全局變量SideTableBuf
中,把SideTableBuf
定義成字符數組其目的是爲了方便計算StripedMap<SideTable>
的內存大小,從而開闢一塊與StripedMap<SideTable>
大小相同的內存。其實能夠把 SideTableBuf
當作一個全局的StripedMap<SideTable>
類型的變量,由於SideTables()
方法已經把返回值SideTableBuf
強轉成StripedMap<SideTable>
類型的變量。下面分析下StripedMap
這個類工具
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
}
複製代碼
從上面定義能夠看出StripedMap<SideTable>
類實際上是包裝了一個結構體的成員變量array
的哈希表,該成員變量是一個裝着PaddedT
類型的數組,PaddedT
這個結構構體實際上就是咱們模板類傳進的SideTable
。所以這裏能夠把array
當作是一個裝着SideTable
的容器,容量爲8或64(運行的平臺不一樣而不一樣)。源碼分析
當系統調用 SideTables()[對象指針]
時,StripedMap<SideTable>
這個哈希表就會在array
中找出對應數組指針的SideTable
類返回,這裏能夠看出其中的一個SideTable
類變量可能對應多個不一樣的對象指針。post
if (slowpath(transcribeToSideTable)) {
sidetable_addExtraRC_nolock(RC_HALF);
}
複製代碼
執行到下面的if語句裏面的 sidetable_addExtraRC_nolock(RC_HALF);
表明通過do
語句的執行邏輯得出extra_rc
已經溢出了,接下來看下溢出處理的實現
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
//已經溢出了 直接返回true
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
//把 delta_rc 左已兩位後與 oldRefcnt 相加 判斷是否有溢出
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
// 溢出處理
// SIDE_TABLE_FLAG_MASK = 0b11 = SIDE_TABLE_DEALLOCATING + SIDE_TABLE_WEAKLY_REFERENCED
// SIDE_TABLE_RC_PINNED 溢出標誌位
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else { //沒有溢出
refcntStorage = newRefcnt;
return false;
}
}
複製代碼
能夠看出extra_c
溢出的時候是把一半值減掉後存進對應對象指針的SideTable
的成員變量RefcountMap refcnts
中。在弄清楚上面代碼邏輯前,先看下幾個重要的宏定義
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
複製代碼
經過宏定義及RefcountMap
的實現(下面會分析)能夠發現refcntStorage
實際上是一個8字節(64位)大小的內存其內存結構及對應的標識位以下圖
根據上面的代碼用this
指針獲取存放在SideTable
內部引用計數refcntStorage
後,會分別判斷這3個標識位都爲0時才執行計數增長的操做,在調用addc
是也會執行 delta_rc << SIDE_TABLE_RC_SHIFT
左移的操做來避開相應的標識位後在相應的內存位上。若是相加後溢出了,會把最高的移除標識位置爲1。
通過sidetable_addExtraRC_nolock
處理後isa指針中的extrc_rc
在溢出的狀況下成功吧一半的數值移存到了對應SideTable
的refcntStorage
哈希表中,從而釋放了isa.extra_rc
的內存繼續記錄retainCount
。
咱們先看下存放extra_rc
溢出部分的RefcountMap
定義:
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
複製代碼
能夠看出 RefcountMap
實際上是DenseMap
的模板類的別名, DenseMap
這是繼承自DenseMapBase
的類,其內部實現能夠看出DenseMap
實際上是一個典型的哈希表(相似oc的NSDictionary
),經過分析能夠發現關於DenseMap
的幾點
KeyT
用 DisguisedPtr<objc_object>
包裝對象指針,此類是對對象指針值(obje_object *)的封裝或說是假裝,使其不收內存泄露測試工具的影響。ValueT
用size_t
代替,size_t
是一個64位內存的unsigned int
KeyInfoT
用DenseMapInfo<KeyT>
代替,在此處就至關於DenseMapInfo<DisguisedPtr<objc_object>
,DenseMapInfo
封裝了比較重要的方法哈希值的獲取用於查找對應Key的內容。主要爲哈希表提供了KeyT的判等isEqual,以及KeyT類型值的hashValue的獲取下面是代碼實現
//Key判等實現,直接用 == 完成判等
static bool isEqual(const T *LHS, const T *RHS) { return LHS == RHS; }
//根據Key獲取對應的hash值
static unsigned getHashValue(const T *PtrVal) {
//ptr_hash調用到下面的內聯函數
return ptr_hash((uintptr_t)PtrVal);
}
#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
key ^= key >> 4;
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
key ^= key >> 4;
key *= 0x5052acdb;
key ^= __builtin_bswap32(key);
return key;
}
#endif
複製代碼
簡化了源碼看主要查找實現
//重寫操做符[]
ValueT &operator[](const KeyT &Key) {
return FindAndConstruct(Key).second;
}
value_type& FindAndConstruct(const KeyT &Key) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return *TheBucket;
return *InsertIntoBucket(Key, ValueT(), TheBucket);
}
//查找實現
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
//存放全部內容的bucket數組
const BucketT *BucketsPtr = getBuckets();
//bucket個數
const unsigned NumBuckets = getNumBuckets();
//沒有內容直接返回
if (NumBuckets == 0) {
FoundBucket = 0;
return false;
}
//根據Val的哈希值算出的bucket的索引 getHashValue調用的是KeyInfo的實現
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (1) {
//從buckets數組拿出對應索引的值
const BucketT *ThisBucket = BucketsPtr + BucketNo;
if (KeyInfoT::isEqual(Val, ThisBucket->first)) { //符合 key == indexOfKey
//賦值外面傳進來的參數
FoundBucket = ThisBucket;
return true;
}
BucketNo += ProbeAmt++;
BucketNo&= (NumBuckets-1);
}
}
複製代碼
首先咱們看下主要處理release
邏輯的方法實現
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
複製代碼
方法主要分爲幾大邏輯模塊
extra_rc --
後位溢出的狀況處理ectra_rc --
後下溢出的狀況處理首先分析執行extra_rc--
後正常未下溢出的狀況,此狀況主要是經過subc
函數讓newisa.bit
與RC_ONE(1ULL<<56)
相加,最後更新isa
的值。
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//計算溢出的標識位
uintptr_t carry;
// extra_rc-- RC_ONE -> (1<<56)在isa中恰好是extra_rc的開始位
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
if (slowpath(carry)) {
goto underflow;//下溢出了, 直接跳轉下溢出的處理邏輯
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits))); // 把newisa.bits 賦值給isa.bits ,並退出 while 循環
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
複製代碼
加入通過subc
函數的運算newisa.bits
發生了下溢出的話,直接跳轉到underflow
的處理邏輯中。下面分析下underflow
的主要邏輯
underflow:
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) { //用SideTabel的refcnts
//爲對應的SideTable加鎖後在操做器內存數據
if (!sideTableLocked) {
sidetable_lock();
sideTableLocked = true;
//修改下 sideTableLocked = true; 從新調用retry
goto retry;
}
// 把一部分的refCount出來賦值給 borrowed
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
//把引用計數 - 1 後賦值給 extra_rc
newisa.extra_rc = borrowed - 1;
//更新isa的extra_rc
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
//下面是處理更新isa值失敗的重試操做
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);
}
}
}
//重試更新isa值仍是失敗的話,把borrowed再次存進對象的SideTable中。再週一遍retry的代碼邏輯(開始的do while位置)
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
//執行到這裏表明成功把對應SideTable的值轉移了部分值到isa.ectra_rc中,併爲對應SideTable類型值加鎖
sidetable_unlock();
return false;
}
else {
//來到else語句的話表明對應SideTable已經沒有存儲額外的retainCount。接下來要執行對象內存釋放的邏輯了。
}
}
複製代碼
經過上面下溢出處理的代碼分析能夠知道,extra_rc--
後發生下溢出的話,系統會優先去查找對象對應SideTable
值中存儲的哈希表refcnts
變量,在經過refcnts
查找到對應對象存儲的8字節內存的count
去一部分出來(大小爲isa.extra_rc
恰好溢出的一半大小),存放到isa.extra_rc
中。若是此時refcnts
取出的值也爲0了就表明對象能夠釋放掉內存了。
對象內存釋放的調用,主要是把isa.deallocating
的標識位置爲1,而後執行SEL_dealloc
釋放對象內存。
// 上面若是 borrowed == 0 來到這裏表明retainCount等於0 對象能夠釋放了
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();
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); //表明引用計數已經等於0 調用dealloc釋放內存
}
return true;
複製代碼
對象經過retain
與release
巧妙地使內部的isa.extra_rc
與外部存儲在對應其自己的SideTable
類中存儲的引用計數值增減有條不紊地進行着加減法。並經過判斷當兩個值都知足必定條件時就執行對象的SEL_dealloc
消息,釋放內存