在上一篇文章中国觉,詳細(xì)分析了IOS內(nèi)存管理的內(nèi)存布局吧恃、內(nèi)存管理方案、引用計(jì)數(shù)等內(nèi)容麻诀,本篇文章將繼續(xù)上篇文章的內(nèi)容探索自動(dòng)釋放池
autoreleasepool
的相關(guān)知識(shí)痕寓。
1、autoreleasepool初探
熟悉OC開發(fā)的都知道蝇闭,在main
函數(shù)中就有@autoreleasepool
這樣一個(gè)東西呻率,其實(shí)這就是自動(dòng)釋放池。那么@autoreleasepool
的底層實(shí)現(xiàn)是什么樣的呢呻引?我們?cè)诿钚兄惺褂?clang -rewrite-objc main.m -o main.cpp
讓編譯器重新改寫這個(gè)文件礼仗,講得到一個(gè)main.cpp文件,打開該文件苞七,找到其中的main函數(shù)藐守。
int main(int argc, const char *argv[])
{
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
return 0;
}
我們可以看到@autoreleasepool
轉(zhuǎn)化成了__AtAutoreleasePool
這樣一個(gè)結(jié)構(gòu)體,那么意味著@autoreleasepool
的本質(zhì)就是__AtAutoreleasePool
結(jié)構(gòu)體蹂风。
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
這個(gè)結(jié)構(gòu)體會(huì)在初始化時(shí)調(diào)用objc_autoreleasePoolPush()
方法卢厂,會(huì)在析構(gòu)時(shí)調(diào)用
objc_autoreleasePoolPop
方法。
這就說(shuō)明了main函數(shù)在實(shí)際工作的時(shí)候是這樣的:
int main(int argc, const char *argv[])
{
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// do whatever you want
objc_autoreleasePoolPop(atautoreleasepoolobj);
return 0;
}
似乎一切都是圍繞著objc_autoreleasePoolPush()
和objc_autoreleasePoolPop
這兩個(gè)方法展開惠啄。那么我們來(lái)看下這兩個(gè)方法的源碼實(shí)現(xiàn):
void *
objc_autoreleasePoolPush(void)
{
// 調(diào)用了AutoreleasePoolPage中的push方法
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
// 調(diào)用了AutoreleasePoolPage中的pop方法
AutoreleasePoolPage::pop(ctxt);
}
上面的兩個(gè)方法看上去是對(duì)AutoreleasePoolPage
對(duì)應(yīng)靜態(tài)方法push
和pop
的封裝慎恒。
2、AutoreleasePoolPage
在runtime中的源碼(objc4-756.2版本)中找到了一段注釋撵渡,這段注釋對(duì)我們理解AutoreleasePoolPage
的底層結(jié)構(gòu)會(huì)有所幫助融柬。
- A thread's autorelease pool is a stack of pointers.
- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
- Thread-local storage points to the hot page, where newly autoreleased objects are stored.
翻譯中文如下:
- 一個(gè)線程的自動(dòng)釋放池是一個(gè)指針的堆棧結(jié)構(gòu)。
- 每個(gè)指針代表一個(gè)需要釋放的對(duì)象或者POOL_BOUNDARY(自動(dòng)釋放池邊界)
- 一個(gè) pool token 就是這個(gè) pool 所對(duì)應(yīng)的 POOL_BOUNDARY 的內(nèi)存地址趋距。當(dāng)這個(gè) pool 被 pop 的時(shí)候粒氧,所有內(nèi)存地址在 pool token 之后的對(duì)象都會(huì)被 release。
- 這個(gè)堆棧被劃分成了一個(gè)以 page 為結(jié)點(diǎn)的雙向鏈表节腐。pages 會(huì)在必要的時(shí)候動(dòng)態(tài)地增加或刪除外盯。
- Thread-local storage(線程局部存儲(chǔ))指向 hot page 摘盆,即最新添加的 autoreleased 對(duì)象所在的那個(gè) page 。
從上面這段注釋中我們可以知道自動(dòng)釋放池是一種棧的結(jié)構(gòu)饱苟,遵循先進(jìn)后出的原則孩擂,每一個(gè)自動(dòng)釋放池都是由一系列的AutoreleasePoolPage
組成的,而AutoreleasePoolPage
是以雙向鏈表的形式連接起來(lái)箱熬。
2.1类垦、AutoreleasePoolPage結(jié)構(gòu)
來(lái)看一下AutoreleasePoolPage
的代碼定義(只列出了關(guān)鍵代碼,部分代碼省略)城须。
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
// AutoreleasePoolPage的大小蚤认,通過(guò)宏定義,可以看到是4096字節(jié)
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
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;//16字節(jié)
id *next;//8字節(jié)
pthread_t const thread;//8字節(jié)
AutoreleasePoolPage * const parent;//8字節(jié)
AutoreleasePoolPage *child;//8字節(jié)
uint32_t const depth;//4字節(jié)
uint32_t hiwat;//4字節(jié)
}
- magic:用來(lái)校驗(yàn)
AutoreleasePoolPage
的結(jié)構(gòu)是否完整酿傍。- *next:next指向的是下一個(gè)
AutoreleasePoolPage
中下一個(gè)為空的內(nèi)存地址(新來(lái)的對(duì)象會(huì)存儲(chǔ)到next
處)烙懦,初始化時(shí)指向begin()
。- thread:保存了當(dāng)前頁(yè)所在的線程(一個(gè)
AutoreleasePoolPage
屬于一個(gè)線程赤炒,一個(gè)線程中可以有多個(gè)AutoreleasePoolPage
)氯析。- *parent:指向父節(jié)點(diǎn),第一個(gè)
parent
節(jié)點(diǎn)為nil
莺褒。- *child:指向子節(jié)點(diǎn)掩缓,最后一個(gè)
child
節(jié)點(diǎn)為nil
。- depth:代表深度遵岩,從0開始你辣,遞增+1。
- hiwat:代表
high water Mark
最大入棧數(shù)尘执。- SIZE:
AutoreleasePoolPage
的大小舍哄,值為PAGE_MAX_SIZE
,4096個(gè)字節(jié)。- POOL_BOUNDARY:只是
nil
的別名誊锭。在每個(gè)自動(dòng)釋放池初始化調(diào)用objc_autoreleasePoolPush
的時(shí)候表悬,都會(huì)把一個(gè)POOL_SENTINEL
push到自動(dòng)釋放池的棧頂,并且返回這個(gè)POOL_SENTINEL
自動(dòng)釋放池邊界丧靡。而當(dāng)方法objc_autoreleasePoolPop
調(diào)用時(shí)蟆沫,就會(huì)向自動(dòng)釋放池中的對(duì)象發(fā)送release
消息,直到第一個(gè)POOL_SENTINEL
温治。
在AutoreleasePoolPage
中的第一個(gè)對(duì)象是存儲(chǔ)在next
后面饭庞,那么就形成如下圖所示這樣一個(gè)結(jié)構(gòu)。
其中的56個(gè)字節(jié)存儲(chǔ)的AutoreleasePoolPage
的成員變量熬荆,其他的區(qū)域存儲(chǔ)加載到自動(dòng)釋放池的對(duì)象舟山。
當(dāng)next==begin()
時(shí)表示AutoreleasePoolPage
為空,當(dāng)next==end()
的時(shí)表示AutoreleasePoolPage
已滿。
2.2捏顺、AutoreleasePoolPage容量
在上一個(gè)小節(jié)的內(nèi)容中我們分析了AutoreleasePoolPage
的結(jié)構(gòu)六孵,了解到每一個(gè)AutoreleasePoolPage
的大小是4096字節(jié),其中56字節(jié)用于存儲(chǔ)成員變量幅骄,剩下的區(qū)域存儲(chǔ)加載到自動(dòng)釋放池的對(duì)象,那么似乎答案呼之欲出本今,一個(gè)AutoreleasePoolPage
可以存儲(chǔ)(4096-56)/8=505個(gè)對(duì)象拆座。但是有一個(gè)注意的點(diǎn),第一個(gè)page存放的需要釋放的對(duì)象的容量應(yīng)該是504個(gè)冠息,因?yàn)樵趧?chuàng)建page的時(shí)候會(huì)在next
的位置插入1POOL_SENTINEL
挪凑。
2.3、push方法
通過(guò)前面小節(jié)的分析逛艰,我們知道objc_autoreleasePoolPush
的本質(zhì)就是調(diào)用push
方法躏碳。我們先來(lái)看下push方法的源碼。
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
在push
方法中實(shí)際上調(diào)用的是autoreleaseFast
方法散怖,并且首先將一個(gè)POOL_BOUNDARY
對(duì)象插入到棧頂菇绵。slowpath
表示小概率發(fā)生。
2.3.1镇眷、autoreleaseFast方法
如下是autoreleaseFast
方法的源碼
static inline id *autoreleaseFast(id obj)
{
// hotPage就是當(dāng)前正在使用的AutoreleasePoolPage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
// 有hotPage且hotPage不滿咬最,將對(duì)象添加到hotPage中
return page->add(obj);
} else if (page) {
// 有hotPage但是hotPage已滿
// 使用autoreleaseFullPage初始化一個(gè)新頁(yè),并將對(duì)象添加到新的AutoreleasePoolPage中
return autoreleaseFullPage(obj, page);
} else {
// 無(wú)hotPage
// 使用autoreleaseNoPage創(chuàng)建一個(gè)hotPage,并將對(duì)象添加到新創(chuàng)建的page中
return autoreleaseNoPage(obj);
}
}
autoreleaseFast
方法的代碼很簡(jiǎn)單欠动,只要是三個(gè)判斷分支永乌。
- 如果有
hotPage
且沒有滿,則調(diào)用add
方法將對(duì)象添加到hotPage中具伍。否則執(zhí)行第2步翅雏。- 如果有
hotPage
但是已經(jīng)滿了,則調(diào)用autoreleaseFullPage
方法初始化一個(gè)新頁(yè)人芽,并將對(duì)象添加到新的AutoreleasePoolPage
中望几。否則執(zhí)行第3步。- 如果沒有
hotPage
啼肩,則調(diào)用autoreleaseNoPage
方法創(chuàng)建一個(gè)hotPage
,并將對(duì)象添加到新創(chuàng)建的page
中
hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage橄妆。
2.3.2、add 添加對(duì)象
add
方法將對(duì)象添加到AutoreleasePoolPage
中祈坠。
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;//將obj存放在next處害碾,并將next指向下一個(gè)位置
protect();
return ret;
}
這個(gè)方法其實(shí)就是一個(gè)壓棧操作,將對(duì)象添加到AutoreleasePoolPage
中赦拘,然后移動(dòng)棧頂指針慌随。
2.3.3、autoreleaseFullPage
autoreleaseFullPage
方法會(huì)重新開辟一個(gè)新的AutoreleasePoolPage
頁(yè),并將對(duì)象添加到其中阁猜。
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 {
// 如果page->child不為空丸逸,那么使用page->child
if (page->child) page = page->child;
// 否則的話,初始化一個(gè)新的AutoreleasePoolPage
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 將找到的合適的page設(shè)置成hotPage
setHotPage(page);
// 將對(duì)象添加到hotPage中
return page->add(obj);
}
遍歷找到未滿的的page剃袍,如果沒有找到則初始化一個(gè)新的page黄刚,并將page設(shè)置為hotPage,同時(shí)將對(duì)象添加到這個(gè)page中民效。
2.3.4憔维、autoreleaseNoPage
如果當(dāng)前內(nèi)存中不存在hotPage
,就會(huì)調(diào)用autoreleaseNoPage
方法初始化一個(gè)AutoreleasePoolPage
畏邢。
id *autoreleaseNoPage(id obj)
{
// Install the first page.
// 初始化一個(gè)AutoreleasePoolPage
// 當(dāng)前內(nèi)存中不存在AutoreleasePoolPage,則從頭開始構(gòu)建AutoreleasePoolPage,也就是其parent為nil
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
// 將初始化的AutoreleasePoolPage設(shè)置成hotPage
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
// 添加一個(gè)邊界對(duì)象(nil)
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
// 將對(duì)象添加到AutoreleasePoolPage中
return page->add(obj);
}
當(dāng)前內(nèi)存中不存在AutoreleasePoolPage
,則從頭開始構(gòu)建AutoreleasePoolPage
,也就是其parent
為nil
业扒。初始化之后,將當(dāng)前頁(yè)標(biāo)記為hotPage
舒萎,然后會(huì)先向這個(gè)page
中添加一個(gè)POOL_SENTINEL
對(duì)象程储,來(lái)確保在pop
調(diào)用的時(shí)候,不會(huì)出現(xiàn)異常臂寝。最后章鲤,將對(duì)象添加到自動(dòng)釋放池中。
2.4交煞、pop方法
上面小節(jié)我們探索了objc_autoreleasePoolPush
,下面我們看看objc_autoreleasePoolPop
咏窿。
objc_autoreleasePoolPop
的本質(zhì)就是調(diào)用pop
方法。
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
上面方法做了如下幾件事:
- 調(diào)用
pageForPointer
獲取當(dāng)前token所在的page素征。- 調(diào)用
releaseUntil
方法釋放棧中的對(duì)象集嵌,直到stop
。- 調(diào)用
child
的kill
方法御毅。
2.4.1根欧、pageForPointer找到page
pageForPointer
方法主要是通過(guò)通過(guò)內(nèi)存地址的操作,獲取當(dāng)前token所在頁(yè)的首地址端蛆。
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;
ASSERT(offset >= sizeof(AutoreleasePoolPage));
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
將指針與頁(yè)面的大小(4096)取模凤粗,可以得到當(dāng)前指針的偏移量。然后將指針的地址減偏移量便可以得到首地址今豆。
2.4.2嫌拣、releaseUntil釋放對(duì)象
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
// 釋放AutoreleasePoolPage中的對(duì)象,直到next指向stop
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
// hotPage可以理解為當(dāng)前正在使用的page
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
// 如果page為空的話呆躲,將page指向上一個(gè)page
// 注釋寫到猜測(cè)這里可以使用if异逐,我感覺也可以使用if
// 因?yàn)楦鶕?jù)AutoreleasePoolPage的結(jié)構(gòu),理論上不可能存在連續(xù)兩個(gè)page都為空
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
// obj = page->next; page->next--;
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
// POOL_BOUNDARY為nil插掂,是哨兵對(duì)象
if (obj != POOL_BOUNDARY) {
// 釋放obj對(duì)象
objc_release(obj);
}
}
// 重新設(shè)置hotPage
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
因?yàn)?code>AutoreleasePool實(shí)際上就是由AutoreleasePoolPage
組成的雙向鏈表灰瞻,因此腥例,*stop
可能不是在最新的AutoreleasePoolPage
中,即hotPage
酝润,這時(shí)需要從hotPage
開始燎竖,一直釋放,直到stop
要销,中間所經(jīng)過(guò)的所有AutoreleasePoolPage
里面的對(duì)象都要釋放构回。
對(duì)象的釋放objc_release
方法請(qǐng)移步前面的文章iOS內(nèi)存管理一:Tagged Pointer&引用計(jì)數(shù)。
2.4.3疏咐、kill方法
kill
方法刪除雙向鏈表中的每一個(gè)page
void kill()
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
AutoreleasePoolPage *page = this;
// 找到鏈表最末尾的page
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
// 循環(huán)刪除每一個(gè)page
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
3捐凭、自動(dòng)釋放池和線程
官方文檔Using Autorelease Pool Blocks中關(guān)于自動(dòng)釋放池和線程的關(guān)系有如下一段描述。
Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.
翻譯成中文如下:
應(yīng)用程序中的每個(gè)線程都維護(hù)自己的自動(dòng)釋放池塊堆棧凳鬓。如果您正在編寫一個(gè)僅限基礎(chǔ)的程序,或者正在分離一個(gè)線程患民,那么您需要?jiǎng)?chuàng)建自己的自動(dòng)釋放池塊缩举。
如果您的應(yīng)用程序或線程是長(zhǎng)生命周期的,并且可能會(huì)生成大量的自動(dòng)釋放對(duì)象匹颤,那么您應(yīng)該使用自動(dòng)釋放池塊(如在主線程上使用AppKit和UIKit);否則仅孩,自動(dòng)釋放的對(duì)象會(huì)累積,內(nèi)存占用會(huì)增加印蓖。如果分離的線程不進(jìn)行Cocoa調(diào)用辽慕,則不需要使用自動(dòng)釋放池塊。
從上面這段秒速我們可以知道自動(dòng)釋放池和線程是緊密相關(guān)的赦肃,每一個(gè)自動(dòng)釋放池只對(duì)應(yīng)一個(gè)線程溅蛉。
4、AutoreleasePool和RunLoop
一般很少會(huì)將自動(dòng)釋放池和RunLoop
聯(lián)系起來(lái)他宛,但是如果打印[NSRunLoop currentRunLoop]
結(jié)果中會(huì)發(fā)現(xiàn)和自動(dòng)釋放池相關(guān)的回調(diào)船侧。
<CFRunLoopObserver 0x6000024246e0 [0x7fff8062ce20]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
<CFRunLoopObserver 0x600002424640 [0x7fff8062ce20]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c1235c), context = <CFArray 0x600001b7afd0 [0x7fff8062ce20]>{type = mutable-small, count = 1, values = (0 : <0x7fc18f80e038>)}}
即App啟動(dòng)后,蘋果會(huì)給RunLoop
注冊(cè)很多個(gè)observers
厅各,其中有兩個(gè)是跟自動(dòng)釋放池相關(guān)的镜撩,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
。\
- 第一個(gè)
observer
監(jiān)聽的是activities=0x1(kCFRunLoopEntry)
队塘,也就是在即將進(jìn)入loop
時(shí)袁梗,其回調(diào)會(huì)調(diào)用_objc_autoreleasePoolPush()
創(chuàng)建自動(dòng)釋放池; - 第二個(gè)
observer
監(jiān)聽的是activities = 0xa0(kCFRunLoopBeforeWaiting | kCFRunLoopExit)
憔古,
即監(jiān)聽的是準(zhǔn)備進(jìn)入睡眠和即將退出loop
兩個(gè)事件遮怜。在準(zhǔn)備進(jìn)入睡眠之前,因?yàn)樗呖赡軙r(shí)間很長(zhǎng)投放,所以為了不占用資源先調(diào)用_objc_autoreleasePoolPop()
釋放舊的釋放池奈泪,并調(diào)用_objc_autoreleasePoolPush()
創(chuàng)建新建一個(gè)新的,用來(lái)裝載被喚醒后要處理的事件對(duì)象;在最后即將退出loop
時(shí)則會(huì)_objc_autoreleasePoolPop()
釋放池子涝桅。
5拜姿、總結(jié)
- 自動(dòng)釋放池是由
AutoreleasePoolPage
以雙向鏈表的方式實(shí)現(xiàn)的。 - 當(dāng)對(duì)象調(diào)用
autorelease
方法時(shí)冯遂,會(huì)將對(duì)象加入AutoreleasePoolPage
的棧中蕊肥。 - 調(diào)用
AutoreleasePoolPage::pop
方法會(huì)向棧中的對(duì)象發(fā)送release
消息。 - 自動(dòng)釋放池和線程是緊密相關(guān)的蛤肌,每一個(gè)自動(dòng)釋放池只對(duì)應(yīng)一個(gè)線程