######物件--id
typedef struct objc_object *id;
struct objc_object {
isa_t _Nonnull isa OBJC_ISA_AVAILABILITY;
};
複製程式碼
######arm64 架構中的 isa_t 結構體 (bits格式一樣,一些資訊的位數不一樣)
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 0 表示普通的 isa 指標,1 表示使用優化,儲存引用計數
uintptr_t has_assoc : 1; // 表示該物件是否包含 associated object,如果沒有,則析構時會更快
uintptr_t has_cxx_dtor : 1; // 表示該物件是否有 C++ 或 ARC 的解構函式,如果沒有,則析構時更快
uintptr_t shiftcls : 33; // 類的指標
uintptr_t magic : 6; // 固定值為 0xd2,用於在除錯時分辨物件是否未完成初始化。
uintptr_t weakly_referenced : 1; // 表示該物件是否有過 weak 物件,如果沒有,則析構時更快
uintptr_t deallocating : 1; // 表示該物件是否正在析構
uintptr_t has_sidetable_rc : 1; // 表示該物件的引用計數值是否過大無法儲存在 isa 指標
uintptr_t extra_rc : 19; // 儲存引用計數值減一後的結果
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
};
複製程式碼
#####引用計數
現在一個物件的引用計數管理有三種情況: 1、TaggedPointer -- 深入理解Tagged Pointer 2、SideTable雜湊表 3、nonpointer--(bits.extra_rc + SideTable)管理引用計數
nonpointer:在64位系統中,為了降低記憶體使用,提升效能,isa中有一部分欄位用來儲存其他資訊。
######RetainCount原始碼,直接體現了三種情況
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this; // 1、TaggedPointer
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) { // 3、nonpointer--(bits.extra_rc + SideTable)管理引用計數
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount(); // 2、SideTable雜湊表
}
複製程式碼
#2、SideTable雜湊表
記憶體管理主要結構程式碼
struct SideTable {
spinlock_t slock; // 保證原子操作的自旋鎖
RefcountMap refcnts; // 引用計數的 hash 表
weak_table_t weak_table; // weak 引用全域性 hash 表
};
複製程式碼
retainCount 是儲存在一個無符號整形中
uintptr_t
objc_object::sidetable_retainCount()
{
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
// it->second 無符號整型(上面的圖),真實的引用計數需要右移2位
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
複製程式碼
####retain原始碼
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)) {
// 引用計數加1,(上圖可知,偏移2位,SIDE_TABLE_RC_ONE = 1<<2)
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();
return (id)this;
}
複製程式碼
####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()) {
// (1)正在釋放
do_dealloc = true;
// 標記dealloc(上圖的第二位)
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// (2)引用計數為0
do_dealloc = true;
// 標記dealloc(上圖的第二位)
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
// (3)正常引用計數減1,(上圖可知,偏移2位,SIDE_TABLE_RC_ONE = 1<<2)
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
if (do_dealloc && performDealloc) {
// (4)do_dealloc為真,執行dealloc方法
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
複製程式碼
看後面幾個判斷。 (1)、如果物件記錄在引用計數表的最後一個:do_dealloc 設定為 true,引用計數數值設定為 SIDE_TABLE_DEALLOCATING(二進位制 00000010)。
2、如果引用計數小於 SIDE_TABLE_DEALLOCATING(就是引用計數等於0)。
3、最後引用計數大於>=1, 就it->second -= SIDE_TABLE_RC_ONE;就是-1。
4、最後,如果 do_dealloc 和 performDealloc(傳入時就已經為 true)都為 ture,執行 SEL_dealloc 釋放物件。方法返回 do_dealloc。
5、呼叫父類dealloc,直到根類NSobject
#3、nonpointer(bits.extra_rc + SideTable) ####retain原始碼
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)) {
// 2、SideTable雜湊表方法
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
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++
// 如果newisa.extra_rc++ 溢位, carry==1
if (slowpath(carry)) {
// 溢位
if (!handleOverflow) {
ClearExclusive(&isa.bits); // 空操作(系統預留)
return rootRetain_overflow(tryRetain);// 再次呼叫rootRetain(tryRetain,YES)
}
// 執行rootRetain_overflow會來到這裡,就把extra_rc對應的數值(一半)存到SideTable
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF; // 溢位了,設定為一半,儲存一半到SideTable
newisa.has_sidetable_rc = true; // 標記借用SideTable儲存
}
// StoreExclusive儲存newisa.bits到isa.bits,儲存成功返回YES
// 這裡while判斷一次就結束了
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 借位儲存:把RC_HALF(一半)存入SideTable
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
複製程式碼
這個方法挺簡單的: 1、其實就是引用計數extra_rc++; 2、extra_rc滿了存一半到SideTable(arm64的extra_rc是19位,完全夠存)
######借位儲存方法
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;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
// 如果借位儲存在這裡還溢位,就當做SIDE_TABLE_RC_PINNED次數(32或64最大位數-1的數值)
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
複製程式碼
#####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;
if (slowpath(!newisa.nonpointer)) {
// 這是2、SideTable雜湊表的
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
// 如果extra_rc==0,extra_rc--會是負數,carry=1
if (slowpath(carry)) {
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa重新賦值
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
// 有借位儲存,rootRelease_underflow重新進入函式
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// 一些鎖的操作
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
goto retry;
}
// 獲取借位引用次數,(獲取次數最大RC_HALF)
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
// extra_rc--
newisa.extra_rc = borrowed - 1;
// 儲存,就是StoreExclusive
// 如果&isa.bits和oldisa.bits相等,那麼就把newisa.bits的值賦給&isa.bits,並且返回true
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// 儲存失敗,重新試一次(重複的程式碼)
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) {
// 還是不成功,把次數放回SideTable,重試retry
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// release結束
sidetable_unlock();
return false;
}
else {
// 如果sidetable也有沒有次數,然後就到下面dealloc階段了
}
}
// 如果沒有借位儲存次數,來到這裡
if (slowpath(newisa.deallocating)) {
// 如果物件已經正在釋放,報錯警告:多次release
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
}
newisa.deallocating = true;
// 儲存bits
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) {
// 呼叫dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
複製程式碼
####NSobject dealloc原始碼
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;
}
複製程式碼
簡單明確的幹了三件事: 1、執行object_cxxDestruct,處理本類的成員變數(父類的由父類處理)。 object_cxxDestruct裡面最終for ( ; cls; cls = cls->superclass){執行.cxx_destruct}; cxx_destruct最後執行處理成員變數: ① strong的objc_storeStrong(&ivar, nil)release物件,ivar賦值nil, ② weak的objc_destroyWeak(& ivar)消除物件weak表中的ivar地址。
2、執行_object_remove_assocations去除和這個物件assocate的物件(常用於category中新增帶變數的屬性,這也是為什麼沒必要remove一遍的原因。
3、執行objc_clear_deallocating,清空引用計數表並清除弱引用表,將所有weak引用指nil(這也就是weak變數能安全置空的所在)。
####物件釋放過程的簡單總結--dealloc
1. 呼叫 -release :isa.bits.extra_rc由0繼續減一時候觸發dealloc,
* 標記物件isa.deallocating = true,物件正在被銷燬,生命週期即將結束.
* 不能再有新的 __weak 弱引用
* 呼叫 [self dealloc] (MRC需要在dealloc方法中手動釋放強引用的變數)
* 繼承關係中每一層的父類 都在呼叫 -dealloc,一直到根類(一般都是NSObject)
2. NSObject 調 -dealloc
* 只做一件事:呼叫 Objective-C runtime 中的 object_dispose() 方法
3. 呼叫 object_dispose()
* objc_destructInstance(obj);
* free(obj);
4. objc_destructInstance(obj)執行三個操作
* if (cxx) object_cxxDestruct(obj); // 釋放變數
(1) strong的objc_storeStrong(&ivar, nil)release物件,ivar賦值nil,
(2) weak ivar,出了作用域,objc_destroyWeak(&ivar) >> storeWeak(&ivar, nil) 將ivar指向nil且ivar的地址從物件的weak表中刪除。
* if (assoc) _object_remove_assocations(obj); // 移除Associate關聯資料(這就是不需要手動移除的原因)
* obj->clearDeallocating(); // 清空引用計數表、清空weak變數表且將所以引用指向nil
複製程式碼
objc_storeStrong原始碼
是對一個strong指標再次賦值,比如
NSObject *obj = [NSObject new];
NSObject *obj2 = [NSObject new];
NSObject *strongObj = obj;
strongObj = obj2;//執行objc_storeStrong(&strongObj, obj2);
{
NSObject *obj = [NSObject new];
} // 出了作用域,執行objc_storeStrong(&obj, nil);
複製程式碼
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
複製程式碼
解析: 1、當前strong指標指向的位置找到舊物件, 2、對新物件執行retain操作,把strong指標從新指向新物件, 3、對舊物件執行release操作。
參考: iOS進階——iOS(Objective-C)記憶體管理·二 ARC下dealloc過程及.cxx_destruct的探究