學(xué)習(xí)了好久的iOS內(nèi)存管理,一直是斷斷續(xù)續(xù)的,現(xiàn)在有時間找了個機會總結(jié)了一下,有時候時間久了好多知識點就會遺忘,希望能將這些點記下來,多看幾次。
前言:虛擬內(nèi)存
移動設(shè)備的內(nèi)存資源是有限的,當App運行時占用的內(nèi)存大小超過了限制后,就會被強殺掉,從而導(dǎo)致用戶體驗被降低。所以,為了提升App質(zhì)量,開發(fā)者要非常重視應(yīng)用的內(nèi)存管理問題。
移動端的內(nèi)存管理技術(shù),主要有兩種:
GC(Garbage Collection,垃圾回收)的標記清除算法;
Apple使用的引用計數(shù)方法。
相比較于 GC 標記清除算法,引用計數(shù)法可以及時地回收引用計數(shù)為0的對象,減少查找次數(shù)。但是,引用 計數(shù)會帶來循環(huán)引用的問題,比如當外部的變量強引用 Block時,Block 也會強引用外部的變量,就會出現(xiàn) 循環(huán)引用。我們需要通過弱引用,來解除循環(huán)引用的問題。
另外,在 ARC(自動引用計數(shù))之前,一直都是通過 MRC(手動引用計數(shù))這種手寫大量內(nèi)存管理代碼的 方式來管理內(nèi)存,因此蘋果公司開發(fā)了 ARC 技術(shù),由編譯器來完成這部分代碼管理工作。但是,ARC依然 需要注意循環(huán)引用的問題。
內(nèi)存管理的演進過程:在最開始的時候,程序是直接訪問物理內(nèi)存,但后來有了多程序多任務(wù)同時運 行,就出現(xiàn)了很多問題。比如,同時運行的程序占用的總內(nèi)存必須要小于實際物理內(nèi)存大小。再比如,程序 能夠直接訪問和修改物理內(nèi)存,也就能夠直接訪問和修改其他程序所使用的物理內(nèi)存,程序運行時的安全就 無法保障。
虛擬內(nèi)存:
由于要解決多程序多任務(wù)同時運行的這些問題,所以增加了一個 中間層 來間接訪問物理內(nèi)存,這個中間層就是虛擬內(nèi)存。虛擬內(nèi)存通過映射,可以將虛擬地址轉(zhuǎn)化成物理地址。
虛擬內(nèi)存會給每個程序創(chuàng)建一個單獨的執(zhí)行環(huán)境,也就是一個獨立的虛擬空間,這樣每個程序就只能訪問自 己的地址空間(Address Space),程序與程序間也就能被安全地隔離開了。
32位的地址空間是 2^32 = 4294967296 個字節(jié),共 4GB,如果內(nèi)存沒有達到 4GB 時,虛擬內(nèi)存比實際的物 理內(nèi)存要大,這會讓程序感覺自己能夠支配更多的內(nèi)存。如同虛擬內(nèi)存只供當前程序使用,操作起來和物理 內(nèi)存一樣高效。
有了虛擬內(nèi)存這樣一個中間層,極大地節(jié)省了物理內(nèi)存。iOS的共享庫就是利用了這一點,只占用一份物理 內(nèi)存,卻能夠在不同應(yīng)用的多份虛擬內(nèi)存中,去使用同一份共享庫的物理內(nèi)存。
1、 引用計數(shù) RetainCount
在iOS開發(fā)中,使用 引用計數(shù) 來管理OC對象的內(nèi)存:
- 一個新創(chuàng)建的OC對象,它的引用計數(shù)是1,當引用計數(shù)減為0 ,OC對象就會被銷毀,釋放其占用的內(nèi)存空間;
- 調(diào)用retain會讓對象的引用計數(shù)+1;調(diào)用release會讓對象的引用計數(shù)-1;
- 內(nèi)存管理總結(jié)經(jīng)驗:
- 當調(diào)用alloc,new,copy,mutablecopy等返回一個對象,在不需要這個對象的時候,需要對這個對象進行release或者autorelease操作;
- 想擁有某個對象,就讓它的引用計數(shù)+1;不想擁有某個對象,就讓它的引用計數(shù)-1;
1.1 引用計數(shù)存放的位置
從64bit開始,對象的引用計數(shù)存放在優(yōu)化過的isa指針中,也可能存放在sideTable中:

- extra_rc:存放的是對象的引用計數(shù)值減1;
- has_sidetable_rc: 如果引用計數(shù)值過大,extra_rc中存放不下,這時候此值為1,對象的引用計數(shù)存放在sidetable中;

1.2 引用計數(shù)存放的位置sidetable和retainCount、release
1.2.1 SideTables與SideTable:
當優(yōu)化過的isa指針中,引用計數(shù)過大存放不下時,就會將引用計數(shù)存放到SideTable中;
SideTables其實是一個哈希表,可以通過對象的指針找到對象內(nèi)容具體存放在哪個SideTable中:

通過指針找到對象引用計數(shù)存放的SideTable:

SideTable對應(yīng)結(jié)構(gòu)如下圖:

在runtime源碼中,NSObje.mm可以看到SideTable結(jié)構(gòu)體源碼:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; //referanceCount:引用計數(shù)表
weak_table_t weak_table;//弱引用表:存放的對象的弱引用指針
};
問題:為什么不是一個SideTable而是多個SideTable?或者(為什么不將所有的對象放到一個table里面,而是放到不同的side-table里面?)
查找或者修改引用計數(shù)的時候是要加鎖的,如果有多個對象同時查找引用計數(shù):
- 只有一張表的話,查詢肯定是需要加鎖,同步有先后順序的;
- 如果是有多張表,就可以異步進行查詢,不同的表之間查詢是沒有影響的;--> 效率更高
1.2.2 retainCount :
SideTable中的引用計數(shù)表中RefcountMap,存放的就是對象的引用計數(shù):retainCount
runtime源碼如下:
_objc_rootRetainCount(id obj){
return obj->rootRetainCount();
}
objc_object::rootRetainCount()
{
//如果是taggerPointer,直接返回指針;
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//判斷是否是優(yōu)化過的isa指針
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;//extra_rc+1 isa指針中存儲的引用計數(shù)值;
if (bits.has_sidetable_rc) {
// has_sidetable_rc值如果為1:表示引用計數(shù)存放在sidetable中
rc += sidetable_getExtraRC_nolock();//細節(jié):注意+=
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
//不是優(yōu)化過的isa指針直接查找SideTable
return sidetable_retainCount();
}
//優(yōu)化過的isa指針在SideTable中查找引用計數(shù)
size_t objc_object::sidetable_getExtraRC_nolock()
{
ASSERT(isa.nonpointer);
//找到對應(yīng)的table
SideTable& table = SideTables()[this];
//table.refcnts(引用計數(shù)表)中找到對應(yīng)計數(shù)it
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
//沒有優(yōu)化過的isa指針在SideTable中查找引用計數(shù)
objc_object::sidetable_retainCount()
{
//從sideTables中找出當前對象的sideTable
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
//table.refcnts(引用計數(shù)表)中找到對應(yīng)計數(shù)it
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;
}
步驟:
- 判斷是否是TaggerPointer,如果是,直接返回指針
- 判斷是否是優(yōu)化過的isa指針:
- 如果是優(yōu)化過的isa指針,先讀取isa指針中存放的引用計數(shù)extra_rc
- 如果has_sidetable_rc為1,代表引用計數(shù)存放在SideTable中;
- 注意:rc = 1 + bits.extra_rc;-->rc += sidetable_getExtraRC_nolock();二者之和;
- 如果不是優(yōu)化過的isa指針,直接去SideTable中去查找引用計數(shù)值;
- 如果是優(yōu)化過的isa指針,先讀取isa指針中存放的引用計數(shù)extra_rc
1.2.3 release:
當調(diào)用alloc,new,copy,mutablecopy等返回一個對象,在不需要這個對象的時候,需要對這個對象進行release或者autorelease操作,讓對象的引用計數(shù)-1;
- 在ARC中,LLVM編譯器會自動幫我們生成對應(yīng)的[xxx release];
- 在MRC中,需要我們手動添加[xxx release];
當對象的引用計數(shù)減為0時,就會調(diào)用dealloc()函數(shù);
- (oneway void)release {
_objc_rootRelease(self);
}
_objc_rootRelease(id obj){
obj->rootRelease();
}
objc_object::rootRelease(bool performDealloc, bool handleUnderflow){
if (isTaggedPointer()) return false;
if (slowpath(!newisa.nonpointer)) {
return sidetable_release(performDealloc);
}
//has_sidetable_rc為1,引用計數(shù)存放在sidetable中
if (slowpath(newisa.has_sidetable_rc)) {
// 從sidetable中讀取對象引用計數(shù)值
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
newisa.extra_rc = borrowed - 1;
// 存儲修改過的引用計數(shù)值
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
return false;
}
}
// Really deallocate.
//如果對象的引用計數(shù)減為0,使用objc_msgSend()發(fā)送消息調(diào)用dealloc()方法;
if (performDealloc) {
objc_msgSend)(this, @selector(dealloc));
}
return true;
}
- 注意在引用計數(shù)減為0時,會發(fā)送消息調(diào)用對象的dealloc()方法銷毀對象,并釋放對象占用的內(nèi)存空間;
1.3 weak指針實現(xiàn)原理
示例如下圖:
YYPerson只是重寫了dealloc方法,添加打印對象釋放時機;
-
當在作用域{}中,LLVM編譯器會自動添加release操作,使得引用計數(shù)-1;
- __strong YYPerson * person1; // 強指針
- __weak YYPerson * person2; // 弱指針
- __unsafe_unretained YYPerson * person3; // 弱指針
1、當沒有任何指針指向[YYPerson alloc]創(chuàng)建出來的對象時,出了{}作用域,直接釋放:

2、 強引用:__strong:person1
當有強引用指針指向[YYPerson alloc]創(chuàng)建出來的對象時,引用計數(shù)+1;

3、弱引用:
- __weak:person2
- __unsafe_unretained:person3
當有弱引用指針指向[YYPerson alloc]創(chuàng)建出來的對象時,引用計數(shù)不變;現(xiàn)象如下:

1.3.1 __weak 和 __unsafe-unretained區(qū)別
在對象釋放以后調(diào)用weak指針: 打印顯示指針為空;

在對象釋放以后調(diào)用:_unsafe-unretained指針,直接崩潰,顯示野指針錯誤(指針指向地址的對象已經(jīng)被釋放)

區(qū)別 :
weak指針在對象銷毀時,dealloc()方法會調(diào)用rootDealloc方法,最終會判斷,如果該對象有弱引用,會將存放弱引用的散列表weakTable清空,所以最終weak指針會置為nil;
__unsafe_unretained不會有將對應(yīng)指針清空的操作,所以不太安全,在對象銷毀釋放以后,再去調(diào)用指針就回造成壞內(nèi)存訪問:EXC-BAD-ACCESS;
注意: weak指針置為nil后,再調(diào)用方法,在objc_msgSend()中,會先判斷,如果消息接收者為空,則直接return,所以不會調(diào)用方法,也不會crash;
1.3.2 objc_initWeak()
在進行編譯過程前,clang 其實對 __weak 做了轉(zhuǎn)換,調(diào)用objc_initWeak,將對應(yīng)的弱引用指針存儲到SideTable中:
NSObject objc_initWeak(&p, 對象指針);
id objc_initWeak(id *location, id newObj) {
// 查看對象實例是否有效
// 無效對象直接導(dǎo)致指針釋放
if (!newObj) {
*location = nil;
return nil;
}
// 這里傳遞了三個 bool 數(shù)值
// 使用 template 進行常量參數(shù)傳遞是為了優(yōu)化性能
// storeWeak:將弱引用指針存放到SideTable中
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
這一塊知識點參考了瓜神詳細講解weak實現(xiàn)原理的博文:
瓜神-弱引用的實現(xiàn)方式
1.4 dealloc()方法
當一個對象的引用計數(shù)減為0時,就會調(diào)用dealloc()方法釋放對象并清理對應(yīng)的內(nèi)存空間
- (void)dealloc {
_objc_rootDealloc(self);
}
_objc_rootDealloc(id obj){
obj->rootDealloc();
}
objc_object::rootDealloc(){
if (isTaggedPointer()) return; // 如果是isTaggedPointer直接return
/*
isa.nonpointer :0,代表普通的指針,存儲著Class、Meta-Class對象的內(nèi)存地址
1,代表優(yōu)化過,使用位域存儲更多的信息
isa.weakly_referenced:是否有被弱引用指向過,如果沒有,釋放時會更快
isa.has_assoc:是否有設(shè)置過關(guān)聯(lián)對象,如果沒有,釋放時會更快
isa.has_sidetable_rc:引用計數(shù)器是否過大無法存儲在isa中,如果為1,那么引用計數(shù)會存儲在一個叫SideTable的類的屬性中
**/
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
free(this);//上述條件都不滿足,直接free(),釋放當前對象
} else {
object_dispose((id)this);//清理其他相關(guān)和引用
}
}
id object_dispose(id obj){
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
// 判斷是否有c++析構(gòu)函數(shù)和關(guān)聯(lián)對象
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// 處理c++析構(gòu)函數(shù)相關(guān):清除成員變量
if (cxx) object_cxxDestruct(obj);
//移除關(guān)聯(lián)對象
if (assoc) _object_remove_assocations(obj);
//將指向當前對象的弱引用指針置為nil
obj->clearDeallocating();
}
return obj;
}
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// 普通的isa指針
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// 優(yōu)化過的isa指針
clearDeallocating_slow();
}
assert(!sidetable_present());
}
objc_object::clearDeallocating_slow(){
//從 SideTables中取出指針對應(yīng)存放的SideTable
SideTable& table = SideTables()[this];
//如果有弱引用,清除弱引用指針
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
//如果引用計數(shù)不是存放在isa中,而是存放在SideTable中,清除SideTable中refcnts(引用計數(shù)表)中的引用計數(shù)
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
}
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) {
objc_object *referent = (objc_object *)referent_id;
//通過弱引用表weak_table和弱引用指針referent,找出weak_table中弱引用對象的索引entry
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
return;
}
// zero out references
//DisguisedPtr<objc_object *> weak_referrer_t;
//weak_referrer_t是一個集合類型
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) {
//遍歷如果referrer == 傳進來的referent,置為nil
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_table對應(yīng)索引entry中的內(nèi)容
weak_entry_remove(weak_table, entry);
}
2. autorelease 和 AutoReleasePool
在MRC環(huán)境下,有些對象的釋放調(diào)用了[XXX autoerlease]來釋放對象,?調(diào)用autorelease的對象不會立即釋放,也就是對象的引用計數(shù)不會立馬 -1;
通過對autorelease方法的研究發(fā)現(xiàn):
調(diào)用autorelease 的對象的生命周期是通過一個叫AutoreleasePoolPage的對象來管理的,調(diào)用autorelease的對象,其實在@autoreleasepool中執(zhí)行了一下操作:
@autoreleasepool {
// atautoreleasepoolobj = objc_autoreleasePoolPush();
執(zhí)行操作
// objc_autoreleasePoolPop(atautoreleasepoolobj);
}
objc_autoreleasePoolPush(): 入棧 --> 將對象加入到AutoreleasePoolPage表中;
objc_autoreleasePoolPop(): 出棧 --> 將對象從AutoreleasePoolPage表中移除;
2.1 AutoReleasePoolPage結(jié)構(gòu)
調(diào)用autoreleasePoolPush()函數(shù)操作時,會將調(diào)用autorelease的對象加入到AutoReleasePoolPage中;
AutoReleasePoolPage其實是:以棧為節(jié)點通過雙向鏈表的形式鏈接起來的數(shù)據(jù)結(jié)構(gòu)

其結(jié)構(gòu)體成員中,有以下注意的地方
- next : 指向的是下一個可以存放元素的位置;
- thread : 線程,AutoReleasePoolPage和線程是對應(yīng)關(guān)系
- parent : 雙向鏈表中的 prev指針,指向上一個AutoReleasePoolPage
- child : 雙向鏈表中的 next指針,指向下一個AutoReleasePoolPage

2.1.1 autoreleasePoolPush
AutoReleasePoolPage是一個 棧的結(jié)構(gòu),棧的特點是: 先進后出 :
如下圖,入棧順序為0-9,出棧順序為9-0:

在執(zhí)行push操作時,例如添加一個obj(3):
先將哨兵對象指向的位置置為nil;
將push進來的對象指針添加到
再將next指針和哨兵對象向上移動;
如下圖:哨兵對象其實是指一個值為POOL_BOUNDARY的值,這個標識了當前autoreleasePool池的起始位置;

autorelease流程:
首先判斷next指針是否在棧頂,每個autorelpool都是4096字節(jié):
- 當next指針指向棧頂?shù)臅r候,當前page已存滿,重新創(chuàng)建一個page對象來存放push進來的對象
- 當next不是棧頂時,直接執(zhí)行入棧操作;

2.1.2 autoreleasePoolPop
執(zhí)行pop操作有以下流程:
- 根絕傳入的哨兵對象找到對應(yīng)的位置
- 對上次執(zhí)行push操作添加的所有對象依次發(fā)送release消息
- 回退next指針到正確的位置(到另一個哨兵對象的位置,一個page對象只有一個next指針,但是哨兵對象可以有多個)
執(zhí)行pop操作前:

執(zhí)行pop操作結(jié)束后:到下一個哨兵對象前的對象都被釋放,并改變next指針的位置。

所以,總結(jié)一下:
調(diào)用push方法會將一個POOL_BOUNDARY(哨兵對象)入棧,并且返回其存放的內(nèi)存地址
調(diào)用pop方法時傳入一個POOL_BOUNDARY(哨兵對象)的內(nèi)存地址,會從最后一個入棧的對象開始發(fā)送release消息,直到遇到這個POOL_BOUNDARY
id *next指向了下一個能存放autorelease對象地址的區(qū)域
2.1.2 多個@autoreleasepool和嵌套使用
在有多個@autoreleasepool{}時,遵循3步:
1、 push入棧;
2、 執(zhí)行代碼;
3、 pop出棧;
多個平級的@autoreleasepool{};

多個嵌套的@autoreleasepool{};

3. autorelease與runloop的關(guān)系
經(jīng)過前面的了解,調(diào)用autorelease的對象都是通過autoreleasePoolPage來管理的,而autoreleasePoolPage結(jié)構(gòu)體對象中,有一個NSThread對象,說明autoreleasePoolPage來管理對象的入棧和出棧和線程有一定的關(guān)系,而線程處理任務(wù)離不開runloop,所以得仔細探究一下runLoop和autorelease之間的聯(lián)系;
我們知道,在Runloop中,runloop有不同的模式,每種Mode下都會有observers,source0,source1,timers,_name,其中sources和timers是runloop需要處理的任務(wù);

在runloop每一次運行循環(huán)中,都會處理一遍所有的timers和blocks,然后進入休眠狀態(tài),如果有事件處理,就被喚醒,再次進行循環(huán),處理一遍這些事情;
但是在runloop進行休眠之前,也就是在狀態(tài)kCFRunLoopBeforeWaiting之前,會處理一些特殊的事情,比如刷新界面的UI:
問題: 在修改UI,背景色或添加一個subview等操作后,是立即生效執(zhí)行的嗎?
答案是:不會立即生效,而是在當前線程進入休眠,也就是kCFRunLoopBeforeWaiting之前進行刷新操作(刷新UI是在主線程,所以當前線程是指主線程);
所以猜測: 調(diào)用autorelease的對象也有可能是在當前線程休眠的時候出棧釋放的;
以下進行驗證:
3.1 添加observer監(jiān)聽runloop的狀態(tài)
runloop的狀態(tài)是一個枚舉,其對應(yīng)的各種狀態(tài)值如下:

有以下兩種方式添加一個observer監(jiān)聽Runloop的狀態(tài):(注意C語言創(chuàng)建的對象最后都需要release)
方式一: 添加一個C語言O(shè)bserverCallBack()函數(shù)回調(diào):
void ObserverCallBack (CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
break;
default:
break;
}
}
- (void)observeRunloopMode{
CFRunLoopObserverRef observeRef = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ObserverCallBack, NULL);
CFRunLoopRef current = CFRunLoopGetCurrent();
CFRunLoopAddObserver( current, observeRef, kCFRunLoopCommonModes);
CFRelease(current);
CFRelease(observeRef);
}
方式二: 直接使用block(這種方式更簡單)
- (void)observeRunloopMode{
CFRunLoopObserverRef observeRef =CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry ----- %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeWaiting:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeWaiting ----- %@-----------",mode);
CFRelease(mode);
break;
}
case kCFRunLoopAfterWaiting:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopAfterWaiting ----- %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeTimers:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeTimers ----- %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopBeforeSources:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopBeforeSources ----- %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit ----- %@",mode);
CFRelease(mode);
break;
break;
}
default:
break;
}
});
CFRunLoopRef current = CFRunLoopGetCurrent();
CFRunLoopAddObserver( current, observeRef, kCFRunLoopCommonModes);
CFRelease(current);
CFRelease(observeRef);
}
有了監(jiān)聽Runloop狀態(tài)的方法,驗證猜測結(jié)果如下:

所以結(jié)論如下:
主線程 的Runloop中注冊了2個Observer :
第1個Observer監(jiān)聽了kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush();(優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前)
-
第2個Observer
監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush();
監(jiān)聽了kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop()
子線程 中:
- 子線程創(chuàng)建的時候就會創(chuàng)建一個autoreleasepool,并且在線程退出的時候,清空autoreleasepool。
結(jié)語
筆記中提到的內(nèi)容大部分總結(jié)自小碼哥底層原理,仔細總結(jié)起來發(fā)現(xiàn)好多細節(jié)要自己實現(xiàn)了才能深刻的理解;
學(xué)無止境,關(guān)于內(nèi)存管理這塊還有很多需要查缺補漏的地方,這篇只是自己的學(xué)習(xí)筆記,有不對的地方請見諒。