記憶體管理系列文章
記憶體管理—MRC時代的手動記憶體管理
記憶體管理—weak的實作原理
記憶體管理——autorelease原理分析
記憶體管理——定時器問題
iOS程式的記憶體布局
經曆過MRC時代的開發者,肯定都用過
autorelease
方法,用于把對象交給
AutoreleasePool
管理,在合适的時候,自動釋放對象。其實所謂的自動釋放對象,就是對所管理的對象調用
release
方法。要想知道
autorelease
方法的原理,首先就需要弄清楚
AutoreleasePool
是個什麼東東。
下面來看一個段MRC環境下的代碼,為什麼要在MRC下讨論這個問題呢?因為ARC會為我們在合适的地方自動加上
autorelease
代碼,并且不允許我們手動調用該方法了,為了友善研究
autorelease
原理,我們還是得回到MRC。
****************** main.m *****************
#import <Foundation/Foundation.h>
#import "CLPerson.h"
int main(int argc, const char * argv[]) {
NSLog(@"pool--start");
@autoreleasepool {
CLPerson *p = [[[CLPerson alloc] init] autorelease];
}
NSLog(@"pool--end");
return 0;
}
************** CLPerson.m **************
#import "CLPerson.h"
@implementation CLPerson
- (void)dealloc
{
NSLog(@"%s", __func__);
[super dealloc];
}
@end
****************** 列印結果 *******************
2019-08-27 16:37:15.141523+0800 Interview16-autorelease[11602:772121] pool--start
2019-08-27 16:37:15.141763+0800 Interview16-autorelease[11602:772121] -[CLPerson dealloc]
2019-08-27 16:37:15.141775+0800 Interview16-autorelease[11602:772121] pool--end
概括一下看到的表面現象:
CLPerson
執行個體對象
p
是在
@autoreleasepool {}
大括号結束的時候被釋放的。
那麼
@autoreleasepool {}
到底做了什麼呢?我們在指令行視窗裡對
main.m
檔案執行如下指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
在生成的中間代碼
main.cpp
中,找到
main
函數的底層實作如下
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
return 0;
}
其實如果你熟悉消息機制,上述的代碼可以轉化成如下形式
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
CLPerson *p = [[[CLPerson alloc] init] autorelease];
}
return 0;
}
我們觀察可發現
@autoreleasepool {}
經過編譯之後發生了如下轉變

這裡多了個
__AtAutoreleasePool
,它其實是個c++的結構體,可以在
main.cpp
裡搜尋到它的定義如下
struct __AtAutoreleasePool {
//構造函數-->可以類比成OC的init方法,在建立時調用
__AtAutoreleasePool()
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析構函數-->可以類比成OC的dealloc方法,在銷毀時調用
~__AtAutoreleasePool()
{
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
如果你還不了解C++文法也無妨,它跟OC的類相似,可以有函數(方法),上面的這個結構體
__AtAutoreleasePool
裡面有已經有兩個函數,
- 一個構造函數
-->__AtAutoreleasePool()
,結構體被建立時調用,用于結構體的初始化atautoreleasepoolobj = objc_autoreleasePoolPush();
- 一個析構函數
-->~__AtAutoreleasePool()
,結構體被銷毀時調用objc_autoreleasePoolPop(atautoreleasepoolobj);
再回到我們的
main
函數,其實它本質上就是下面這個形式
上面是單層
@autoreleasepool {}
的情況,那麼如果有多層
@autoreleasepool {}
嵌套在一起,就可以按照同樣的規則來拆解
objc_autoreleasePoolPush() & objc_autoreleasePoolPop()
接下來我們就來探究一下這兩個函數的實作邏輯。在objc4源碼的
NSObject.mm
檔案裡可以找到它們的實作
*************** NSObject.mm (objc4) ******************
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
可以看到,它們分别調用了C++類
AutoreleasePoolPage
的
push()
和
pop()
函數。要想繼續深入後續函數的實作邏輯,我們需要先來看一看這個
AutoreleasePoolPage
的内部結構,它的内容不少,有大量函數,但是我們首先需要理清楚它的成員變量,這些是可變化的,可操控的,是以去掉函數和一些靜态常量,可以将
AutoreleasePoolPage
結構簡化如下
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
根據其命名,中文釋義成自動釋放池頁,有個頁的概念。我們知道自動釋放池,是用來存放對象的,這個“頁”就說明釋放池的結構體應該有頁面篇幅限制(記憶體空間大小)。具體多大呢?來看一下
AutoreleasePoolPage
的兩個函數
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
begin()
函數傳回一個指針,指向自身最後一個成員變量之後的記憶體位址(相當于越過了自身所占用的記憶體空間)
end()
裡面有一個
SIZE
,我們看看它的定義
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
********************************************
#define PAGE_MAX_SIZE PAGE_SIZE
********************************************
#define PAGE_SIZE I386_PGBYTES
********************************************
#define I386_PGBYTES 4096 /* bytes per 80386 page */
可以看到,
SIZE
實際上是
4096
。這就是說
end()
函數,得到的是一個指針,指向
AutoreleasePoolPage
對象位址之後的第
4096
個位元組的記憶體位址。
通過以上掌握的資訊,我們先抛出結論,然後再繼續通過源碼加深了解。
每個對象占
AutoreleasePoolPage
個位元組,其中成員變量共占用
4096
*
8位元組
=
7
。剩餘的
56個位元組
4040
個位元組的空間就是用來存儲自動釋放對象的。
因為一個
對象的記憶體是有限的,程式裡面可能有很多對象會被加入自動釋放池,是以可能會出現多個
AutoreleasePoolPage
對象來共同存放自動釋放對象。所有的
AutoreleasePoolPage
對象是以雙向連結清單的形式(資料結構)連接配接在一起的。
AutoreleasePoolPage
對象的各成員變量含義如下
AutoreleasePoolPage
magic_t const magic;
指向
id *next;
内下一個可以用來存放自動釋放對象的記憶體位址
AutoreleasePoolPage
自動釋放池所屬的線程,說明它不能跟多個線程關聯。
pthread_t const thread;
指向上一頁釋放池的指針
AutoreleasePoolPage * const parent;
指向下一頁釋放池的指針
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
![]()
記憶體管理——autorelease原理分析
【第一次AutoreleasePoolPage::push();】
接下來,我們就正式開始研究
AutoreleasePoolPage::push();
。假設我們現在是處在項目的main函數的第一個
@autoreleasepool {}
開始的地方,也就是整個程式将會第一次去調用
push()
函數:
# define POOL_BOUNDARY nil
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {//Debug模式下,每個autorelease pool都會建立新頁
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {//标準情況下,調用autoreleaseFast()函數
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
其中
POOL_BOUNDARY
就是
nil
的宏定義,忽略Debug模式,我們隻看正常模式,那麼
push()
将會調用
autoreleaseFast(POOL_BOUNDARY)
得到一個
id *dest
并将其傳回給上層函數。檢視一下這個
autoreleaseFast()
,看看它到底能給我們傳回什麼
static inline id *autoreleaseFast(id obj)
{
//拿到目前可用的AutoreleasePoolPage對象page
AutoreleasePoolPage *page = hotPage();
//(1)如果page存在&&page未滿,則直接增加obj
if (page && !page->full()) {
return page->add(obj);
} else if (page) {//(2)如果滿了,則調用autoreleaseFullPage(obj, page);
return autoreleaseFullPage(obj, page);
} else {//(3)如果沒有頁面,則調用autoreleaseNoPage(obj);
return autoreleaseNoPage(obj);
}
}
因為是整個程式第一次push操作,是以page對象還不存在,是以會按照情況(3)走,也就是
autoreleaseNoPage(obj);
,實作如下
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
/*--"No page"
1.可以表示目前還沒有任何pool被建立(pushed)
2.也可以表示已經建立了一個empty placeholder pool(空釋放池占位符),隻是還沒添加任何内容
*/
assert(!hotPage());
//标簽-->是否需要增加額外的POOL_BOUNDARY
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
/*
如果存在EmptyPoolPlaceholder(空占位符pool),就修改标簽為true,
後面就需要依據此标簽增加額外的POOL_BOUNDARY
*/
pushExtraBoundary = true;
}
/*
如果傳入的obj不等于POOL_BOUNDARY(nil)并且找不到目前pool(丢失了),傳回nil
*/
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
/*
♥️♥️♥️♥️如果傳入的是POOL_BOUNDARY,并且不在Debug模式,
會調用setEmptyPoolPlaceholder()設定一個EmptyPoolPlaceholder
*/
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
// 初始化第一個AutoreleasePoolPage
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//将其設定成目前頁(hot)
setHotPage(page);
// 根據pushExtraBoundary标簽決定是否多入棧一個POOL_BOUNDARY
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// 将傳入的obj入棧,通過 add()函數
return page->add(obj);
}
因為此時還沒有建立過
AutoreleasePoolPage
,并且也沒有設定過
EmptyPoolPlaceholder
,是以程式會命中代碼中♥️♥️♥️♥️标記出的代碼,調用
setEmptyPoolPlaceholder();
,該函數實作如下
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
********************************************
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}
可以看到實際上就是将
key
與
(id*)1
綁定起來,這個
key
是一個靜态常量,最後将這個
(id*)1
作為一個空釋放池池占位符傳回,這樣整個程式的第一個
push()
函數結束,結果是生成了一個
EMPTY_POOL_PLACEHOLDER (也就是(id*)1)
作為釋放池占位符。
【第一次調用autorelease】
接着上面的過程,我們在
push()
後,第一次對某個對象執行
autorelease
方法時,看一下
autorelease
的内部做了什麼,先找到其源碼如下
- (id)autorelease {
return ((id)self)->rootAutorelease();//?️從這裡往下走
}
************************************************
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();//?️從這裡往下走
}
************************************************
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);//?️從這裡往下走
}
************************************************
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);//⚠️最終走到了這個方法
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
通過逐層遞進,我們看到
autorelease
方法最終又來到了
autoreleaseFast()
函數
static inline id *autoreleaseFast(id obj)
{
//拿到目前可用的AutoreleasePoolPage對象page
AutoreleasePoolPage *page = hotPage();
//(1)如果page存在&&page未滿,則直接增加obj
if (page && !page->full()) {
return page->add(obj);
} else if (page) {//(2)如果滿了,則調用autoreleaseFullPage(obj, page);
return autoreleaseFullPage(obj, page);
} else {//(3)如果沒有頁面,則調用autoreleaseNoPage(obj);
return autoreleaseNoPage(obj);
}
}
那麼這一次,我們看看第一句代碼裡面
hotPage();
得到的是什麼
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
//如果檢查到key有綁定EMPTY_POOL_PLACEHOLDER,傳回nil
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;//将目前頁對象傳回
}
因為我們一開始将
key
與
EMPTY_POOL_PLACEHOLDER
綁定過,是以這裡傳回空,表明目前頁空,還未被建立,是以我們傳回到
autoreleaseFast
方法裡面,将會調用
autoreleaseNoPage(obj)
函數,根據我們上面對這個函數步驟的注釋,這一次程式應該會走到函數的最後一部分
主要做了下面幾件事:
- 初始化第一個
AutoreleasePoolPage
- 将其設定成目前頁(hot)
- 最初的
會使EMPTY_POOL_PLACEHOLDER
置為pushExtraBoundary
,是以這裡需要為第一個true
先入棧一個AutoreleasePoolPage
POOL_BOUNDARY
- 最後用
将傳入的自動釋放對象add(obj)
入棧obj
上面
add()
函數的具體功能,其實就是将
obj
的值指派給目前
AutoreleasePoolPage
的
next
指針指向的記憶體空間,然後
next
再進行
++
操作,移向下一段可用記憶體空間,友善下一次存放自動釋放對象的時候使用。如下
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;//先指派,再++
protect();
return ret;
}
另外需要注意一下這裡的
setHotPage(page)
函數,實作如下
static inline void setHotPage(AutoreleasePoolPage *page)
{
if (page) page->fastcheck();
tls_set_direct(key, (void *)page);
}
它的作用就是把目前新建立的
AutoreleasePoolPage
與
key
綁定起來,日後
hotPage()
函數就可以通過
key
直接拿到目前頁。
####【再一次調用autorelease】
如果我們繼續對新的對象執行
autorelease
操作,同樣會來到函數,但由于
AutoreleasePoolPage
對象已經存在了,如果目前
page
未滿,會走如下函數
也就是直接通過
add(obj)
函數将
obj
對象入棧
我們之前說過,一個
AutoreleasePoolPage
對象能存放的自動釋放對象數量是有限的,一個自動釋放對象就是一個指針,占8位元組,而
AutoreleasePoolPage
對象可用的空間是4040個位元組,也就是可以存放505個對象(指針),是以一頁
AutoreleasePoolPage
是有可能滿頁的,這個時候,
autoreleaseFast
就會調用
autoreleaseFullPage(obj, page);
函數,它的實作如下
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {//通過child指針拿到下一個沒有滿的page對象
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);//先将上面擷取的page設定為目前頁(hot)
return page->add(obj);//通過add函數将obj存入該page
}
其實上面就是通過
AutoreleasePoolPage
對象的
child
指針去尋找下一個未滿的
page
。
AutoreleasePoolPage
對象之間是通過
child
和
parent
指針形成的雙向連結清單結構,就是為了在這個時候使用的。同樣,在清空釋放池對象的時候,如果目前釋放池完全空了,則會通過
parent
指針去尋找上層的釋放池。
【再一次AutoreleasePoolPage::push();】
除了系統在
main
函數裡加上的最初的一層
@autoreleasepool {}
之外,有時候我們自己的代碼裡面可能會也會使用
@autoreleasepool {}
,友善對一些對象進行更為靈活的記憶體管理。那麼我們手動加的
@autoreleasepool {}
肯定是嵌套在main函數
@autoreleasepool {}
内部的,相當于
int main(int argc, const char * argv[]) {
@autoreleasepool {//這是系統加的第一層
@autoreleasepool {}//這是我們可能會添加的内層嵌套
}
}
現在我們再次來看一下這一次
AutoreleasePoolPage::push();
會如何執行。同樣程式會執行到
autoreleaseFast(POOL_BOUNDARY);
POOL_BOUNDARY
會被傳入
autoreleaseFast
函數,并且也會通過
add()
或者
autoreleaseFullPage()
被添加到
AutoreleasePoolPage
對象的頁空間上。其實就是和普通的
[obj autorelease]
的流程一樣,隻不過這次是
obj
=
POOL_BOUNDARY
,顯然這是為了一個新的
@autoreleasepool{}
做準備。
POOL_BOUNDARY
到底是拿來幹嘛的呢?一會你就知道了。
分析完了源碼,現在通過圖例來展示一下
@autoreleasepool
的實作原理。
【假設】為友善展示每頁
隻能存放3個釋放對象,如下
AutoreleasePoolPage
![]()
記憶體管理——autorelease原理分析
autorelease對象什麼時候回調用release方法呢?
這個問題就要搞清楚
@autoreleasepool{}
的另一半
AutoreleasePoolPage::pop(atautoreleasepoolobj);
做了什麼。一起來看一看
其中的核心函數便是
releaseUntile(stop)
,這裡的
stop
實際上傳入的就是
POOL_BOUNDARY
,進入該函數
void releaseUntil(id *stop)
{
while (this->next != stop) {//?如果next指向POOL_BOUNDARY,跳出循環?
//?拿到目前頁
AutoreleasePoolPage *page = hotPage();
//??目前頁如果為空,通過parent拿到上一個AutoreleasePoolPage對象作為目前頁
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
//???通過 --next 拿到目前頁棧頂的對象
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
//????如果obj不是POOL_BOUNDARY,就進行[obj release]
objc_release(obj);
}
}
setHotPage(this);
}
pop()
核心步驟已經在上面函數裡的注釋展現出來。也就是說,當最内層的
@autoreleasepool{}
作用域結束調用其對應的
pop()
函數時,會從
AutoreleasePoolPage
連結清單的目前頁裡面找到棧頂的對象,逐個開始釋放,直到遇到
POOL_BOUNDARY
就停下來,這樣,就代表這一層的
@autorelease{}
内所包含的所有對象都完成了
release
方法調用。
當程式走到上一層的
@autoreleasepool{}
作用域結束的地方,又回執行上面的流程,對其包含的對象一次調用
release
方法。可以通過下圖的示例來體會一下。
AutoreleasePool與RunLoop
通過上面的研究,我們知道
@autoreleasepool{}
的作用,實際上就是在作用域的頭和尾分别調用了
objc_autoreleasePoolPush();
和
objc_autoreleasePoolPop()
函數,但是在iOS項目當中,
@autoreleasepool{}
的作用域是什麼時候開始,什麼時候結束呢?這就需要了解我們之前研究過的另一個知識點RunLoop。我們知道,除非我們手動啟動子線程的RunLoop,否則程式裡面隻有主線程有RunLoop,這是系統預設開啟的。下面我們來看一下主線程的RunLoop肚子裡都有什麼寶貝。
我們可以随便建立一個iOS項目,在
ViewController
的
viewDidLoad
方法裡可以直接列印目前RunLoop對象(即主線程的RunLoop對象)
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
@end
列印結果是洋洋灑灑的一大堆,如果你還不熟悉RunLoop的結構,可以參考我的Runloop的内部結構與運作原理,裡面應該說的比較清楚了。我們可以在列印結果的
common mode items
部分,找到兩個跟
autorelease
相關的
observer
,如下圖所示
具體如下
<CFRunLoopObserver 0x600003f3c640 [0x10a2fdae8]>
{
valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d),
context =
<CFArray 0x6000000353b0 [0x10a2fdae8]>
{
type = mutable-small, count = 1, values = (0 : <0x7f91ff802058>)
}
}
<CFRunLoopObserver 0x600003f3c500 [0x10a2fdae8]>
{
valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d),
context =
<CFArray 0x6000000353b0 [0x10a2fdae8]>
{
type = mutable-small, count = 1, values = (0 : <0x7f91ff802058> )
}
}
我們可以看到,這兩個監聽器分監聽的狀态分别是
-
(對應十進制的activities = 0xa0
)160
-
(對應十進制的activities = 0x1
1
)
這兩個狀态怎麼解讀呢?我們可以在CF架構的RunLoop源碼裡面找到對應的定義
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),************十進制1---(進入loop)
kCFRunLoopBeforeTimers = (1UL << 1),****十進制2
kCFRunLoopBeforeSources = (1UL << 2),**十進制4
kCFRunLoopBeforeWaiting = (1UL << 5),***十進制32----(loop即将休眠)
kCFRunLoopAfterWaiting = (1UL << 6),*****十進制64
kCFRunLoopExit = (1UL << 7),**************十進制128----(退出loop)
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
根據RunLoop狀态的枚舉值可以看出,
160 = 128 + 32
,也就是說
-
=(kCFRunLoopExit | kCFRunLoopBeforeWaiting)activities = 0xa0
-
activities = 0x1
=(kCFRunLoopEntry)
是以這三個狀态被監聽到的時候,就會調用
函數。這個函數實際上是按照下圖的示意運作_wrapRunLoopWithAutoreleasePoolHandler
記憶體管理——autorelease原理分析 - 監聽到kCFRunLoopEntry事件,調用
objc_autoreleasePoolPush();
- 監聽到kCFRunLoopBeforeWaiting事件,調用
,然後調用objc_autoreleasePoolPop()
objc_autoreleasePoolPush();
- 監聽到kCFRunLoopExit事件,調用
objc_autoreleasePoolPop()
根據上面的分析,我們可以總結,除了程式啟動(對應kCFRunLoopEntry)和程式退出(對應kCFRunLoopExit)會調用一次
objc_autoreleasePoolPush();
和
objc_autoreleasePoolPop()
外,程式的運作過程中,每當RunLoop即将休眠,被
observer
監聽到kCFRunLoopBeforeWaiting狀态時,會先調用一次
objc_autoreleasePoolPop()
,這樣就将目前的
autoreleasepool
裡面的對象逐個調用
release
方法,相當于清空釋放池子;緊接着再調用一次
objc_autoreleasePoolPush();
,相當于開啟一個新的釋放池,等待RunLoop醒來後的下一次循環使用。
自動釋放池的對象什麼時候會被調用release方法呢?
RunLoop的每一圈循環過程中,調用過
方法的對象(也就是被加入
autorelease
的對象),會在當次循環即将進入休眠狀态的時候,被調用
AutoreleasePoolPage
方法,也可以說是被釋放了。
release
好了,AutoreleasePool的原理以及它和RunLoop的關系就分析到這裡