Objective-C
內存管理的核心思想就是經過對象的引用計數來對內存對象的生命週期進行控制。說直白一點,就是調用retain
會加1,調用release
就會減1,引用計數清零或者調用dealloc
就銷燬。c++
引用計數
引用計數,即爲對象被持有的次數。是內存管理的核心點。下面咱們來看一個關於引用計數的例子:安全
- (void)testRefCount {
NSObject *obj = [NSObject alloc];
NSLog(@"==refCount==%ld==", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
複製代碼
運行程序,結果爲1。但是alloc
的流程中並無對引用計數操做的流程,那麼這個打印爲何是1呢?來看看retainCount
的源碼:ide
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
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();
}
複製代碼
能夠看到,引用計數的總值就是isa
裏面extra_rc
的取值和散列表中引用計數表的取值外加1,咱們新alloc
的對象,引用計數打印爲1就是由於這個加的這個1,其實新alloc
出來的對象引用計數爲0。函數
那麼引用計數是存儲在哪裏的,retain
和release
究竟是如何處理的呢?下面咱們先來看一下retain
:post
retain
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
inline id
objc_object::retain()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
複製代碼
能夠看到,調用objc_retain
首先會判斷是不是isTaggedPointer
,若是是就直接返回。接着會判斷對象沒有自定義retain/release
方法就會調用rootRetain
,不然就經過objc_msgSend
發送SEL_retain
消息。性能
id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
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;
// 獲取isa
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
// 不是nonpointer isa 散列表處理
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// 若是正在deallocating 不作處理
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
// 是nonpointer isa extra_rc++
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);
}
// 超出以後一半存到散列表中,一半放在extra_rc 而且處理isa的extra_rc標誌位和has_sidetable_rc
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)) {
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
複製代碼
進入到rootRetain
會進行以下操做:ui
isTaggedPointer
,TaggedPointer
是不須要維護引用計數的,直接返回。TaggedPointer
,就獲取對象的isa
,判斷是否是nonpointer isa
nonpointer isa
,交給散列表處理,對引用計數進行++
操做,而後返回deallocating
,是的話直接返回nonpointer isa
,對isa
的標誌位extra_rc
執行++
操做extra_rc
能存儲的範圍了,就將其中的一半存在extra_rc
,並把has_sidetable_rc
標誌位置爲1。而後拷貝另一半放入散列表進行保存。散列表是多張表,因爲性能和安全的考慮,是多張而不是一張,可是多張並非每一個對象就一張。散列表的存儲引用計數的方式以下,也就是對存儲的引用計數進行++
操做:this
bool
objc_object::sidetable_tryRetain()
{
SideTable& table = SideTables()[this];
bool result = true;
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
table.refcnts[this] = SIDE_TABLE_RC_ONE;
} else if (it->second & SIDE_TABLE_DEALLOCATING) {
result = false;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second += SIDE_TABLE_RC_ONE;
}
return result;
}
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;
}
複製代碼
release
retain
是引用計數+1
,而release
是引用計數-1
,流程是相輔相成的。調用release
仍是會進入到objc_release
方法。spa
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
inline void
objc_object::release()
{
assert(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
複製代碼
能夠看到,調用objc_release
也會先判斷是不是isTaggedPointer
,若是是就直接返回。接着會判斷對象沒有自定義retain/release
方法就會調用rootRelease
,不然就經過objc_msgSend
發送SEL_release
消息。3d
bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
// isTaggedPointer 不作處理 直接返回
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)) {
// 不是nonpointer_isa 對散列表中的引用計數進行處理
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// nonpointer_isa 對isa的extra_rc 進行--操做
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
if (slowpath(carry)) {
// 若是不夠減,則須要去散列表的引用計數表中借位
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
newisa = oldisa;
// 此時isa的extra_rc已經清零 沒有計數了
// isa的散列表引用標誌位有值
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
// 遞歸調用
return rootRelease_underflow(performDealloc);
}
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// 散列表沒加鎖,加鎖 遞歸
goto retry;
}
// 把散列表裏存儲的引用計數取出來
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// 散列表中的引用計數若是大於0
if (borrowed > 0) {
// 對散列表的引用計數作--操做 而後存入isa的extra_rc
newisa.extra_rc = borrowed - 1;
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// 若是沒有成功的存入 isa的extra_rc 那就再存一遍
......
}
if (!stored) {
// 二次存入仍是沒有成功 就把數據放回到散列表的引用計數表
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// 從散列表借位--成功
sidetable_unlock();
return false;
}
else {
// 散列表的引用計數也是空的
}
}
// isa 沒有在deallocating中 那拋出異常
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
}
// 將isa置爲deallocating,而後再來遞歸一遍
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__sync_synchronize();
if (performDealloc) {
// 發送一個SEL_dealloc的消息
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
複製代碼
梳理一下,進入到rootRelease
以後的流程以下:
isTaggedPointer
,TaggedPointer
是不須要維護引用計數的,直接返回。TaggedPointer
,就獲取對象的isa
,判斷是否是nonpointer isa
nonpointer isa
,交給散列表處理,對引用計數進行--
操做,若是散列表的引用計數清零,就須要對該對象發送SEL_dealloc
信息,執行dealloc
操做,而後返回。nonpointer_isa
就對isa
的extra_rc
進行--
操做,當extra_rc
計數爲0,不夠減的時候,就須要從散列表的引用計數表借位減。isa
的has_sidetable_rc
是否有值,有值就進行第6步,沒有值就進行第8步SEL_dealloc
信息,執行dealloc
操做。isa
的extra_rc
,返回。若是沒有存入成功,則進行2次遞歸存儲,若是仍是沒有成功,就將計數存入散列表,繼續進行一次遞歸操做。isa
是否正處於deallocating
,若是沒有那就拋出異常。SEL_dealloc
信息,執行dealloc
操做。散列表的引用計數表release
引用計數的操做:
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
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) {
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) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
複製代碼
dealloc
當頁面銷燬或者對象銷燬的時候就會進入dealloc
方法進行相關的處理。
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
// TaggedPointer 不用處理引用計數
if (isTaggedPointer()) return; // fixme necessary?
// 是nonpointer
// 沒有弱引用表、沒有關聯對象、沒有c++析構器、沒有散列表引用計數
// 直接釋放
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);
}
}
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// 存在c++析構函數 調用析構
if (cxx) object_cxxDestruct(obj);
// 存在關聯對象 刪除關聯對象
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// 非non-pointer isa
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// non-pointer isa
clearDeallocating_slow();
}
assert(!sidetable_present());
}
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
// 清除弱引用表
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 清除散列表中的引用計數表的相關信息
table.refcnts.erase(it);
}
table.unlock();
}
void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
// 清除弱引用表
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 清除散列表中的引用計數表的相關信息
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
複製代碼
總結一下,調用dealloc
方法的流程:
TaggedPointer
,則不用處理引用計數,返回nonpointer_isa
,且沒有弱引用表、沒有關聯對象、沒有c++析構器、沒有散列表引用計數,那就直接釋放,不然進入第3步,調用object_dispose()
objc_destructInstance(obj)
,而後在調用free(obj)
objc_destructInstance()
方法中,判斷若是存在cxx
析構器則須要調用析構方法,若是存在關聯對象須要刪除關聯對象。弱引用表的釋放詳見weak
原理,此處就不贅述。
retain
流程release
流程dealloc
流程