通過以下方法查看iOS的引用計數(shù)管理:
- alloc
- retain
- release
- retainCount
- dealloc
源碼版本:objc4-723
alloc
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
當(dāng)我們調(diào)用類的alloc方法時,調(diào)用的方法棧如上所示,我們來看最后一部分代碼即可。
在這一部分代碼中,有兩個宏定義:
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))
__builtin_expect的主要作用就是幫助編譯器判斷條件跳轉(zhuǎn)的預(yù)期值,避免因執(zhí)行jmp跳轉(zhuǎn)指令造成時間浪費(fèi)。
編譯器優(yōu)化時,根據(jù)條件跳轉(zhuǎn)的預(yù)期值,按正確地順序生成匯編代碼,把“很有可能發(fā)生”的條件分支放在順序執(zhí)行指令段,而不是
jmp指令段(jmp指令會打亂CPU的指令執(zhí)行順序,大大影響CPU指令執(zhí)行效率)。
在本例中,if else句型編譯后, 一個分支的匯編代碼緊隨前面的代碼,而另一個分支的匯編代碼需要使用jmp指令才能訪問到,很明顯通過jmp訪問需要更多的時間, 在復(fù)雜的程序中,有很多的if else句型,又或者是一個有if else句型的庫函數(shù),每秒鐘被調(diào)用幾萬次,通常程序員在分支預(yù)測方面做得很糟糕,編譯器又不能精準(zhǔn)的預(yù)測每一個分支,這時jmp產(chǎn)生的時間浪費(fèi)就會很大,函數(shù)__builtin_expert()就是用來解決這個問題的。
所以,這里的邏輯就是如果slowpath(checkNil && !cls)為0時,該函數(shù)返回nil,為1時走接下里的邏輯。
在接下來的邏輯中,fastpath(!cls->ISA()->hasCustomAWZ()首先會判斷當(dāng)前class或super class是否實現(xiàn)了allocWithZone:方法(AWZ即AllocWithZone),如果沒有實現(xiàn)這個方法,最終都會調(diào)用C語言函數(shù)calloc申請一塊內(nèi)存空間。
如果實現(xiàn)了AWZ方法,就會執(zhí)行allocWithZone:。
總結(jié),調(diào)用alloc時,經(jīng)過一系列方法調(diào)用,最終會在內(nèi)存中申請一塊內(nèi)存空間,但此時并沒有設(shè)置引用計數(shù)。
retain
- (id)retain {
return ((id)self)->rootRetain();
}
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
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;
}
在上面代碼塊中,因為篇幅所限省去了rootRetain()的具體實現(xiàn)代碼,在rootRetain()中實際上調(diào)用了sidetable_retain()方法,也就是最后那部分代碼。
在了解最后一部分代碼前,需要了解一些概念上的知識,有助于更好的理解系統(tǒng)對對象引用計數(shù)管理的實現(xiàn)原理。
為了管理所有對象的引用計數(shù)以及對象的所有弱引用指針,系統(tǒng)創(chuàng)建了一個全局的SideTables,SideTables里存的是一個個的SideTable,每個SideTable實際上都是一個結(jié)構(gòu)體,如下:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
//省略部分代碼
SideTables本質(zhì)上是一個全局的hash表,key值為對象的內(nèi)存地址。
回到sidetable_retain()方法,首先會根據(jù)當(dāng)前對象的指針到SideTables中獲取SideTable:
SideTable& table = SideTables()[this];
然后在SideTable的結(jié)構(gòu)體當(dāng)中獲取當(dāng)前對象的引用計數(shù)值:
size_t &refcntStorage = table.refcnts[this];
需要注意的是,這兩次查找都是hash查找。
然后再對應(yīng)用計數(shù)值進(jìn)行+1操作:
refcntStorage += SIDE_TABLE_RC_ONE;
以上,就是進(jìn)行retain操作時,系統(tǒng)對對象引用計數(shù)操作的具體實現(xiàn)。
release
- (oneway void)release {
((id)self)->rootRelease();
}
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
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) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
篇幅所限,省略了rootRelease()的代碼實現(xiàn),在rootRelease()中會調(diào)用sidetable_release(),也就是最后一部分代碼,我們可以將最后一部分代碼進(jìn)行簡化,簡化后的代碼如下所示:
SideTable &table = SideTables()[this];
RefcountMap ::iterator it = table.refcnts.find(this);
it->second -= SIDE_TABLE_RC_ONE;
首先根據(jù)對象的內(nèi)存地址到hash表中查找SideTable:
SideTable &table = SideTables()[this];
然后根據(jù)查找到的SideTable和對象的內(nèi)存地址獲取其引用計數(shù)并-1:
RefcountMap ::iterator it = table.refcnts.find(this);
it->second -= SIDE_TABLE_RC_ONE;
另外,當(dāng)對象需要被回收時,系統(tǒng)還會利用objc_msgSend向?qū)ο蟀l(fā)送dealloc消息。
以上,就是進(jìn)行release操作時,系統(tǒng)對對象引用計數(shù)操作的具體實現(xiàn)。
retainCount
- (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();
}
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()) {
// this is valid for SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
這里把retainCount方法調(diào)用棧的全部代碼都貼出來了,主要來看最后一部分代碼,也就是sidetable_retainCount()方法的代碼。
我們也可以帶著一個問題來看這段代碼,問題是:
為什么剛創(chuàng)建完的對象,它的retainCount是0?
在這個方法的實現(xiàn)中,首先會根據(jù)對象的內(nèi)存地址獲取到SideTable:
SideTable& table = SideTables()[this];
然后緊接著聲明了一個局部變量refcnt_result,值為1:
size_t refcnt_result = 1;
所以,剛alloc出來的對象,在引用計數(shù)表(SideTables)中是沒有這個對象的key-value映射的,所以table.refcnts.find(this)讀出來的值是0,所以如果這里是0,就將局部變量refcnt_result的值返回,也即是1。
如果table.refcnts.find(this)讀出來的值不是0,則會把查找的結(jié)果做向右偏移的操作然后+1,返回給調(diào)用方:
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
以上,就是進(jìn)行retainCount操作時,系統(tǒng)對獲取對象引用計數(shù)的具體實現(xiàn)原理。
dealloc
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
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);
}
}
以上是dealloc的方法調(diào)用棧,從最后一個方法開始看,在這個方法中有一個if判斷條件比較多:
- 首先判斷有沒有使用非指針型
isa(nonpointer_isa) - 判斷是否有
weak指針指向它 - 判斷是否有關(guān)聯(lián)對象
- 判斷內(nèi)部實現(xiàn)是否涉及到有
C++相關(guān)的內(nèi)容 - 當(dāng)前對象的引用計數(shù)是否是通過
SideTable來維護(hù)的
只有當(dāng)當(dāng)前對象既不是nonpointer_isa,也沒有弱引用,還沒有涉及到C++、關(guān)聯(lián)對象、沒有使用SideTable存儲相關(guān)引用計數(shù),才會調(diào)用C函數(shù)free()進(jìn)行釋放,否則,就調(diào)用object_dispose()對象清除函數(shù)進(jìn)行清除。
再來看object_dispose()函數(shù)的實現(xiàn):
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();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
先來看objc_destructInstance函數(shù),如果obj對象存在的話,會先判斷當(dāng)前對象是否有用到涉及到C++相關(guān)的東西,如果有,稍后會調(diào)用object_cxxDestruct函數(shù)進(jìn)行銷毀操作。
還會判斷當(dāng)前對象是否擁有關(guān)聯(lián)對象,如果有的話稍后就會調(diào)用_object_remove_assocations函數(shù)進(jìn)行清除操作,所以我們并不需要在dealloc方法中顯式的清除關(guān)聯(lián)對象。
最后會調(diào)用obj的clearDeallocating函數(shù),函數(shù)實現(xiàn)如下:
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
其會調(diào)用兩個函數(shù),分別是:
sidetable_clearDeallocating()clearDeallocating_slow()
(1)sidetable_clearDeallocating()函數(shù)實現(xiàn)如下:
void
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];
// clear any weak table items
// clear extra retain count and deallocating bit
// (fixme warn or abort if extra retain count == 0 ?)
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();
}
其中還會調(diào)用weak_clear_no_lock()函數(shù),實現(xiàn)如下:
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
weak_clear_no_lock函數(shù)有兩個參數(shù),weak_table及referent_id。
weak_table就是弱引用表,記錄了指向該對象的弱引用指針。
referent_id就是當(dāng)前正在被銷毀的對象。
這里會把referent_id強(qiáng)轉(zhuǎn)成objc_object類型的結(jié)構(gòu)體指針,記做referent,根據(jù)referent到weak_table中獲取entry,進(jìn)而通過weak_entry_t獲取弱引用數(shù)組referrers,繞后遍歷這個referrers數(shù)組,將指向該對象的弱引用指針全部置為nil,最后從weak_table中把這個entry刪除。
所以,當(dāng)一個對象有一個弱引用指針指向它的時候,當(dāng)這個對象被廢棄之后它的弱引用指針會被自動置為nil。
(2)clearDeallocating_slow()函數(shù)實現(xiàn)如下:
NEVER_INLINE 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();
}
table.refcnts.erase(this);函數(shù)的主要作用是從引用計數(shù)表中擦除該對象的引用計數(shù)。
總結(jié)一下,當(dāng)對象被釋放時,也就是在系統(tǒng)的dealloc方法實現(xiàn)中,系統(tǒng)會銷毀該對象所使用的C++相關(guān)的內(nèi)容,還會刪除該對象的關(guān)聯(lián)對象,并且將指向該對象的弱引用指針全部置為nil,這些操作完成后,會從全局的引用計數(shù)表中擦除該對象的引用計數(shù)。
以上,就是dealloc的實現(xiàn)原理。
內(nèi)存管理方案
分類
- TaggedPointer
針對于小對象,如NSNumber、NSDate等。 - NONPOINTER_ISA
對于64位架構(gòu)下的應(yīng)用程序采用這種內(nèi)存管理方案,在64位架構(gòu)下,ISA指針占64個bit位,實際上有32位或40位就夠用了,剩余的是浪費(fèi)的,Apple為了提高內(nèi)存的利用率,在剩余的bit位當(dāng)中存儲一些關(guān)于內(nèi)存管理的數(shù)據(jù)內(nèi)容,也被稱為非指針型的isa。 - 散列表
包括引用計數(shù)表和弱引用表。
NONPOINTER_ISA


- indexed
0:代表它是一個純的isa指針,里面的內(nèi)容直接代表當(dāng)前對象的類對象的地址。
1:代表isa指針里面存儲的不僅是類對象的地址,還有一些內(nèi)存管理的數(shù)據(jù)。 - has_assoc
當(dāng)前對象是否有關(guān)聯(lián)對象
0:沒有
1:有 - has_cxx_dtor
當(dāng)前對象是否有使用到C++語言方面的內(nèi)容 - 后續(xù)33位
當(dāng)前對象的類對象的指針地址 - magic
略,不影響分析 - weakly_referenced
當(dāng)前對象是否有弱引用指針 - deallocating
表示當(dāng)前對象是否在進(jìn)行dealloc操作 - has_sidetable_rc
指當(dāng)前isa指針當(dāng)中如果所存儲的引用計數(shù)已經(jīng)達(dá)到了上限的話,需要外掛一個sidetable這樣的數(shù)據(jù)結(jié)構(gòu)去存儲相關(guān)的引用計數(shù)內(nèi)容。 - extra_rc
額外的引用計數(shù),當(dāng)引用計數(shù)在一個很小的值得范圍內(nèi)就會存到isa指針當(dāng)中,而不是由單獨(dú)的引用計數(shù)表去存儲引用計數(shù),
散列表方式
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);
};
在非嵌入式系統(tǒng)中,SideTables管理著64個SideTable,每個SideTable有3個元素:
-
spinlock_t自旋鎖 -
RefcountMap引用計數(shù)表 -
weak_table_t弱引用表
為什么不是一個SideTable,而是由多個SideTable組成SideTables?
如果使用一個SideTable管理系統(tǒng)所有對象的引用計數(shù),那么當(dāng)我們在多線程中對其中一個對象進(jìn)行retain、release等操作時就會對這個SideTable加鎖,以此保證數(shù)據(jù)訪問的安全,那么在這個過程中實際上就產(chǎn)生了效率的問題,下一個對象要想進(jìn)行操作(比如修改引用計數(shù)),就必須要等鎖釋放只會才能操作這張表,如果成千上萬個對象同操作一張表,那么效率是極其低下的。
系統(tǒng)為了解決效率低下的問題,系統(tǒng)引入了分離鎖的技術(shù)方案,我們可以把對象所對應(yīng)的引用計數(shù)表可以分拆成多個部分,比如可以分拆成8個,那么就會對8個表分別加鎖,假如A對象屬于表1,B對象屬于表2,那么此時就可以進(jìn)行并行操作,提高了訪問效率。
如何實現(xiàn)快速分流?
指通過一個對象的指針,如何快速定位到它屬于哪張SideTable?
SideTable的本質(zhì)是一張Hash表,這張Hash表中可能有64張具體的SideTable用以存儲不同對象的引用計數(shù)表和弱引用表。
Hash表的key是對象指針經(jīng)過hash函數(shù)計算出一個值,這個值決定對象所屬的SideTable是哪一個,或者說在數(shù)組中(SideTables)的位置是第幾個。
Hash查找
eg:給定值是對象的內(nèi)存地址,目標(biāo)值是數(shù)組(SideTables)下標(biāo)索引。
f(ptr) = (uintptr_t)ptr % array.count
其他涉及到的技術(shù)
-
spinlock_t slock;自旋鎖 -
RefcountMap refcnts;引用計數(shù)表 -
weak_table_t weak_table;弱引用表
spinlock_t
- 自旋鎖是一種忙等的鎖,指當(dāng)前鎖已經(jīng)被其他線程獲取,當(dāng)前線程會不斷探測這個鎖是否有被釋放,如果被釋放掉自己會第一時間獲取這個鎖。
- 信號量是如果獲取不到這個鎖的時候,會把自己線程進(jìn)行阻塞休眠,等到其他線程釋放這個鎖的時候喚醒當(dāng)前線程。
- 適用于輕量訪問,因為對于引用計數(shù)的操作只是簡單的
+1 -1操作,這種操作都是輕量的操作,所以在輕量的訪問場景下,都可以使用自旋鎖。
RefcountMap
實際上是一個Hash表,可以通過對象的指針查找到對應(yīng)的引用計數(shù),查找的過程也是哈希查找,提高查找效率。
- size_t共有64位
- 第一位是
weakly_referenced,表示對象是否有弱引用,0就是沒有,1就是有。 - 第二位是
deallocating,表示當(dāng)前對象是否正在dealloc。 - 其余位存儲的就是對象的實際引用計數(shù)值,具體的引用計數(shù)值實際上要偏移兩位,因為前面有兩位是
weakly_referenced和deallocating,我們要把這兩位去掉。
- 第一位是
weak_table_t
實際上也是一張Hash表,key為對象指針,value為weak_entry_t,weak_entry_t是一個結(jié)構(gòu)體數(shù)組,數(shù)組存儲的每一個對象就是弱引用指針,也就是在代碼中定義的__weak變量,這個變量的指針就存儲在weak_entry_t中。
完。