Autoreleasepool
Autoreleasepool的結(jié)構(gòu)
我們的main函數(shù)中定義一個自動釋放池
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
然后通過clang查看源碼實現(xiàn)
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
可以看到我們的@autoreleasepool轉(zhuǎn)換成了__AtAutoreleasePool,__AtAutoreleasePool的實現(xiàn)如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool有兩個方法,一個是push(),一個是pop()。
通過符號斷點的方式可以確定objc_autoreleasePoolPush在objc的源碼中,然后我們找到對應(yīng)的源碼:
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
調(diào)用了AutoreleasePoolPage的push()函數(shù)。AutoreleasePoolPage是一頁,相當(dāng)于自動釋放池中的好多對象組成了這樣的一頁,然后每一頁又連起來形成了一個雙向的鏈表結(jié)構(gòu)。定位到AutoreleasePoolPage的定義:
/***********************************************************************
Autorelease pool implementation
// 先進(jìn)后出
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.
**********************************************************************/
class AutoreleasePoolPage : private AutoreleasePoolPageData
通過注釋我們可以發(fā)現(xiàn):
- AutoreleasePoolPage是個棧的結(jié)構(gòu),也就是先進(jìn)后出;
- AutoreleasePoolPage中的節(jié)點有兩種:要釋放的對象以及pool boundary(也就是邊界值)。
- page會組成一個雙向鏈接的結(jié)構(gòu);
- tls 線程存儲;
然后看下AutoreleasePoolPageData的實現(xiàn):
struct AutoreleasePoolPageData
{
magic_t const magic; // 16
__unsafe_unretained id *next; //8
pthread_t const thread; // 8
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; //8
uint32_t const depth; // 4
uint32_t hiwat; // 4
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
next是定位偏移;thread為關(guān)聯(lián)的線程;parent為父節(jié)點;child為子節(jié)點;
下面我們看下AutoreleasePoolPage的初始化方法:
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
可見,初始化方法中調(diào)用了AutoreleasePoolPageData的初始化方法,傳入了begin()。begin()就是AutoreleasePoolPage的地址除去上面的那個magic、next、thread、parrent、child等屬性后的位置。也就是開始存儲pointer的地方了。
自動釋放池的結(jié)構(gòu)如下,每個AutoreleasePoolPage作為一個雙向鏈表的節(jié)點。AutoreleasePoolPage的頭部位置存儲著一些屬性:magic、next等。

下面我們向自動釋放池中添加一些對象,然后打印一下
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i<5; i++) {
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"%@", obj);
}
_objc_autoreleasePoolPrint();
}
return 0;
}
打印結(jié)果如下:
objc[18996]: ##############
objc[18996]: AUTORELEASE POOLS for thread 0x1000d1dc0
objc[18996]: 6 releases pending.
objc[18996]: [0x100804000] ................ PAGE (hot) (cold)
objc[18996]: [0x100804038] ################ POOL 0x100804038
objc[18996]: [0x100804040] 0x1006b8ee0 NSObject
objc[18996]: [0x100804048] 0x101100200 NSObject
objc[18996]: [0x100804050] 0x101300020 NSObject
objc[18996]: [0x100804058] 0x10063d6c0 NSObject
objc[18996]: [0x100804060] 0x1006b5e60 NSObject
objc[18996]: ##############
地址0x100804000到0x100804038存儲的是AutoreleasePoolPage的一些頭部屬性信息。然后從0x100804038開始存儲了6個對象,但是我們自己是創(chuàng)建了5個對象。還有一個就是我們哨兵對象,也就是邊界對對象。這個邊界對象是自動釋放池的邊界,當(dāng)嵌套使用自動釋放池的時候,每個自動釋放池都會有對象的邊界。
當(dāng)我們自動釋放池的對象超過505的時候,就會進(jìn)行分頁,創(chuàng)建新的AutoreleasePoolPage。也就是說一個AutoreleasePoolPage只能裝505個對象,注意因為第一個需要有個邊界對象,所以只能有504個對象。
Autoreleasepool的壓棧
首先找到objc_autoreleasePoolPush函數(shù),調(diào)用了push函數(shù)
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
push函數(shù)中調(diào)用autoreleaseFast函數(shù)
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;
}
autoreleaseFast函數(shù)中,如果存在page并且page沒有滿,就會調(diào)用page->add(obj)函數(shù)向page中添加對象;如果page已滿了,調(diào)用autoreleaseFullPage;如果還沒有創(chuàng)建page,那么使用autoreleaseNoPage進(jìn)行創(chuàng)建。
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
- 首先我們來看下創(chuàng)建page的函數(shù)
autoreleaseNoPage
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
主要就是創(chuàng)建了Page,然后設(shè)置為聚焦page,如果是第一個page就先插入邊界,然后再插入對象。
- 然后看下
page->add(obj)函數(shù)向存在的page中添加對象。
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
很簡單,就是移動next的位置,指向obj。
- 最后我們看下page滿的情況:
autoreleaseFullPage函數(shù)實現(xiàn)
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
一個doWhile循環(huán),查找child,也就是雙鏈表中的那個節(jié)點。知道找到?jīng)]有裝滿的page,如果找到最后找不到,就創(chuàng)建一個新的。然后設(shè)置為hotPage,最后添加對象進(jìn)去。
上面就是我們的壓棧過程。
Autoreleasepool的出棧
首先是objc_autoreleasePoolPop函數(shù),該函數(shù)的參數(shù)是調(diào)用pagePush的時候返回的對象。也就是該page的上下文標(biāo)識。
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
然后調(diào)用pop函數(shù),pop函數(shù)的主要內(nèi)容為:
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token);
stop = (id *)token;
return popPage<false>(token, page, stop);
根據(jù)上下文找到對應(yīng)的page,然后調(diào)用popPage函數(shù)。popPage函數(shù)的試下如下:
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && 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)用page->releaseUntil(stop);釋放對象。后面就是對page的kill處理以及setHotPage處理。下面看下releaseUntil函數(shù):
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
}
一個while循,移動next指,依次給對象發(fā)送release消息,如果page為空,就開始處理父節(jié)點,設(shè)置父節(jié)點為hotPage,直到遍歷到邊界。
Autoreleasepool的嵌套
下面的代碼中,我們開啟了一個子線程,子線程會自動創(chuàng)建一個自動釋放池。
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"\n\n obj = %@",obj);
_objc_autoreleasePoolPrint();
});
_objc_autoreleasePoolPrint();
}
然后我們得到下面的打印結(jié)果:
2020-03-22 22:03:55.610864+0800 KCObjcTest[20399:607121] objc = <NSObject: 0x100686c80>
objc[20399]: ##############
objc[20399]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[20399]: 2 releases pending.
objc[20399]: [0x100812000] ................ PAGE (hot) (cold)
objc[20399]: [0x100812038] ################ POOL 0x100812038
objc[20399]: [0x100812040] 0x100686c80 NSObject
objc[20399]: ##############
2020-03-22 22:03:55.612082+0800 KCObjcTest[20399:607493]
obj = <NSObject: 0x1010026e0>
objc[20399]: ##############
objc[20399]: AUTORELEASE POOLS for thread 0x70000e5c0000
objc[20399]: 2 releases pending.
objc[20399]: [0x102802000] ................ PAGE (hot) (cold)
objc[20399]: [0x102802038] ################ POOL 0x102802038
objc[20399]: [0x102802040] 0x1010026e0 NSObject
objc[20399]: ##############
因為自動釋放池是和線程相關(guān)的,所以主線程和子線程都會創(chuàng)建對應(yīng)線程的AutoReleasePool,然后分別存儲一個邊界對象和一個正常的對象。
下面我們在子線程中再次手動創(chuàng)建一個autoreleasepool,那么子線程就會有兩個autoreleasepool了。
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"\n\n obj = %@",obj);
_objc_autoreleasePoolPrint();
}
});
_objc_autoreleasePoolPrint();
}
打印結(jié)果如下:
2020-03-22 22:07:02.159829+0800 KCObjcTest[20445:609384] objc = <NSObject: 0x100712a10>
objc[20445]: ##############
objc[20445]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[20445]: 2 releases pending.
2020-03-22 22:07:02.161109+0800 KCObjcTest[20445:609762]
obj = <NSObject: 0x100644f60>
objc[20445]: [0x102803000] ................ PAGE (hot) (cold)
objc[20445]: [0x102803038] ################ POOL 0x102803038
objc[20445]: [0x102803040] 0x100712a10 NSObject
objc[20445]: ##############
objc[20445]: ##############
objc[20445]: AUTORELEASE POOLS for thread 0x700007752000
objc[20445]: 3 releases pending.
objc[20445]: [0x101801000] ................ PAGE (hot) (cold)
objc[20445]: [0x101801038] ################ POOL 0x101801038
objc[20445]: [0x101801040] ################ POOL 0x101801040
objc[20445]: [0x101801048] 0x100644f60 NSObject
objc[20445]: ##############
Program ended with exit code: 0
可見子線程中的page中變成了3個對象,有兩個邊界。分別標(biāo)識這兩個autoreleasepool的作用域。但是一個線程值創(chuàng)建了一個page。
RunLoop
RunLoop的底層實現(xiàn)是個doWhile循環(huán)。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
依賴于RunLoop的六大事件:

我們通過CFRunLoopGetMain來獲取runLoop。
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
_CFRunLoopGet0的主要實現(xiàn):創(chuàng)建一個dict,然后將線程和runloop對應(yīng)著放到dict中。所以說RunLoop和線程是一一對應(yīng)的。
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 進(jìn)行綁定 dict[@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
RunLoop是個結(jié)構(gòu)體,包含的以下內(nèi)容:
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
_pthread為綁定的線程,還有包含的modes、items。他們的關(guān)系圖如下:

一個runLoop對應(yīng)一個線程,對應(yīng)N個Mode。一個Mode對應(yīng)N個source、timer、observer。
RunLoop調(diào)用timer等事件
下面我們看下timer事件是怎么依賴于RunLoop執(zhí)行的呢?
首先我們創(chuàng)建一個timer,然后在timer的執(zhí)行block的地方打上斷點,查看堆棧調(diào)用情況。

在timer的block執(zhí)行之前的函數(shù)調(diào)用順序為:
__CFRunLoopRun,__CFRunLoopDoTimers(timer事件可能會有很多,所以是個集合體), __CFRunLoopDoTimer, __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ 如果是其他的事件的話,流程相似,只不過是換成對應(yīng)的
__CFRunLoopDoSources0、__CFRunLoopDoSource1、__CFRunLoopDoObservers、__CFRunLoopDoBlocks。
RunLoop的函數(shù)實現(xiàn)
- 通知觀察者即將進(jìn)入RunLoop;
- 使用
dispatch_source_t構(gòu)建一個定時器來控制runLoop的超時退出。CFRunLoopRun() 默認(rèn)的超時時間很大,所以直接設(shè)置為不超時,此時就不需要定時器了; - 然后進(jìn)入一個doWhile循環(huán);
- 這里有個內(nèi)循環(huán),用于接收等待端口的消息。進(jìn)入此循環(huán)后,線程進(jìn)入休眠,直到收到新消息才跳出該循環(huán),繼續(xù)執(zhí)行run loop;
調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個事件喚醒。
1、一個基于 port 的Source 的事件;
2、一個 Timer 到時間了;
3、RunLoop 自身的超時時間到了;
4、被其他什么調(diào)用者手動喚醒。
RunLoop循環(huán)退出的條件:
1、進(jìn)入loop時標(biāo)記了stopAfterHandle參數(shù),也就是處理完事件就返回,那么處理完成事件后直接退出;
2、timer到期了;
3、被外部調(diào)用者強制停止了,通過CFRunLoopStop函數(shù)設(shè)置的;
4、設(shè)置停止,通過_CFRunLoopStopMode設(shè)置停止;
5、source/timer/observer一個都沒有了。
