autoreleasepool前言
下面是我們自己創(chuàng)建了一個autoreleasepool,創(chuàng)建的對象有自動添加到自動釋放池,和手動添加到釋放池的。當(dāng)我們自己創(chuàng)建了autoreleasepool然后把對象添加進(jìn)去,這個就是手動添加到釋放池。每個autoreleasepool都會對應(yīng)一個runloop。如果手動添加到autoreleasepool里這個就和runloop沒有關(guān)系了,出了autoreleasepool作用域就會被釋放。

通過下圖的寫法發(fā)現(xiàn)內(nèi)存會暴增

通過下圖的寫法發(fā)現(xiàn)內(nèi)存不會暴增,因為我們在其位置上添加了autoreleasepool,其出了這個autoreleasepool就會被釋放掉。

AutoreleasePool底層:
首先我們寫個使用@autoreleasepool的釋放池,代碼如下,然后進(jìn)行clang命令進(jìn)行一個操作。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
clang后的代碼,在這里點擊__AtAutoreleasePool進(jìn)去發(fā)現(xiàn)其是一個struct的結(jié)構(gòu)體。
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_v4jdthx95753k1gbfyy30w0w0000gn_T_main_cf4d78_mi_0);
}
return 0;
}
__AtAutoreleasePool構(gòu)造函數(shù)和析構(gòu)函數(shù)。
struct __AtAutoreleasePool {
構(gòu)造函數(shù) __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
析構(gòu)函數(shù) ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
通過上面其實AutoreleasePool底層代碼可以寫成這幾個:
objc_autoreleasePoolPush()
NSLog(@"Hello, World!")
objc_autoreleasePoolPop(atautoreleasepoolobj)
我們通過匯編調(diào)試也可以看到AutoreleasePool其實其是走objc_autoreleasePoolPush和objc_autoreleasePoolPop方法

objc_autoreleasePoolPush方法
下面是objc_autoreleasePoolPush執(zhí)行的代碼,其中::符號是一個命名空間,就是說明AutoreleasePoolPage去調(diào)用push()方法
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
下面是官方對自動釋放池的介紹:
Autorelease pool implementation
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.
A thread's autorelease pool is a stack of pointers.
線程的?動釋放池是?個指針堆棧。(說明?動釋放池肯定和線程是有關(guān)聯(lián)的)
Each pointer is either an object to release, or POOL_BOUNDARY which is an
autorelease pool boundary.
每個指針要么是將要釋放的對象,要么是?個POOL_BOUNDARY,作為?動釋放池的邊界。
(?動釋放池??的數(shù)據(jù)有倆種類型,?種是將要被釋放的對象,另?種是
POOL_BOUNDARY,超出POOL_BOUNDARY,就相當(dāng)于不在當(dāng)前釋放池的范圍之內(nèi)了。)
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.
有?個指向該池的 POOL_BOUNDARY 的指針。當(dāng)池被彈出時,每個?哨兵更熱的對象都會被
釋放。(在調(diào)?pop函數(shù)的時候,整個?動釋放池??的對象都會被釋放。)
The stack is divided into a doubly-linked list of pages. Pages are added and
deleted as necessary.
棧被分成?個雙向鏈接的??列表。根據(jù)需要添加和刪除??。(?動釋放池是?個雙向列表
的棧結(jié)構(gòu)。它的每?個節(jié)點都是AutoreleasePoolPage。)
Thread-local storage points to the hot page, where newly autoreleased objects
are stored.
線程本地存儲指向熱?,其中存儲了新的?動釋放對象。(?動釋放池??的數(shù)據(jù)也有線程緩
存)
下面是AutoreleasePoolPage里的data數(shù)據(jù),其內(nèi)部里是存儲一些數(shù)據(jù)。
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
struct AutoreleasePoolEntry {
uintptr_t ptr: 48;
uintptr_t count: 16;
static const uintptr_t maxCount = 65535; // 2^16 - 1
};
static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
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)
{
}
};
下面是AutoreleasePoolPageData里的成員變量意思:
magic 用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整;
next 指向最新添加的 autoreleased 對象的下一個位置,初始化時指向 begin() ;
thread 指向當(dāng)前線程;
parent 指向父結(jié)點,第一個結(jié)點的 parent 值為 nil ;
child 指向子結(jié)點,最后一個結(jié)點的 child 值為 nil ;
depth 代表深度,從 0 開始,往后遞增 1;
hiwat 代表 high water mark 最大入棧數(shù)量標(biāo)記
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);
}
}
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
下面的是POOL_BOUNDARY也就是一個哨兵(?動釋放池的邊界)代碼。
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
當(dāng)?shù)谝淮芜M(jìn)來時候,會進(jìn)行AutoreleasePoolPage進(jìn)行創(chuàng)建,然后把其加入到hotPage里,hotPage也就是我們進(jìn)行設(shè)置的頁。 page->add(POOL_BOUNDARY)就是把哨兵對象添加到page里。
// 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);
當(dāng)AutoreleasePoolPage不是第一次創(chuàng)建時候就會走下面的autoreleaseFast方法進(jìn)行創(chuàng)建。也是通過hotPage進(jìn)行創(chuàng)建,然后判斷page是否滿了,如果沒有滿了的話就進(jìn)行一個添加到page里。
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);
}
}
AutoreleasePoolPageData使用
下面我們看到AutoreleasePoolPage其實就是調(diào)用AutoreleasePoolPageData里的數(shù)據(jù),在AutoreleasePoolPageData里有個begin()參數(shù),這個是begin()點進(jìn)去其實是一個內(nèi)存平移的操作。
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
直接在autoreleasepool里創(chuàng)建p對象是不會進(jìn)入到autoreleasepool進(jìn)行管理的,其是通過ARC進(jìn)行控制其釋放和銷毀。通過alloc,new ,copy,mutablecopy,allocwith這些創(chuàng)建的對象在autoreleasepool里是不會放到autoreleasepool里進(jìn)行管理的。 那如果我們要添加到自動釋放池,需要在對象前面加__autoreleasing,這樣就會受autoreleasepool釋放池進(jìn)行管理( __autoreleasing HPWPerson *p = [ [HPWPerson alloc] init])。其中我們也可以通過系統(tǒng)提供的_objc_autoreleasePoolPrint()進(jìn)行打印釋放池里的對象。如果是通過stringwithformat這類創(chuàng)建的話就會自動添加到autoreleasepool里無需在代碼前加入__autoreleasing,其中需要注意stringwithformat后面的string要大于9位數(shù),如果小于等于9位數(shù),那么這個就是taggerPoint小對象了,這個是不會添加到autoreleasepool里的。我們可以通過_objc_autoreleasePoolPrint函數(shù)進(jìn)行打印驗證。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
HPWPerson *p = [ [HPWPerson alloc] init];
}
return 0;
}
下面是通過_objc_autoreleasePoolPrint的打印,發(fā)現(xiàn)其值添加到page里了,其中用的是__autoreleasing進(jìn)行修飾。

下面是沒有__autoreleasing進(jìn)行修飾,發(fā)現(xiàn)其沒有加入到autoreleasepool里

其實添加__autoreleasing這個也就是在底層操作調(diào)用obje_autorelease,在其里面最終調(diào)用的是autroreleaseFast這個函數(shù)。autroreleaseFast這個函數(shù)我們之前說過,其會調(diào)用創(chuàng)建page然后進(jìn)行一個添加操作,也就是添加到autoreleasepool里。
思考1:當(dāng)我們把下面紅圈里的代碼放到我們手寫的自動釋放池autoreleasepool外,想想這句代碼會被放到自動釋放遲里嗎?

我們打印后發(fā)現(xiàn)其String代碼還是被加到了自動釋放遲中,這個是為什么呢,其實這個是和runloop有關(guān)系,當(dāng)我們程序運行的時候,其就會有開啟一個runloop,這個runloop會自動的幫我們創(chuàng)建自動釋放池。

下面是runloop運行的示意圖,當(dāng)APP啟動的時候,主線程的runloop是自動開啟的,在循環(huán)的時候,其會注冊兩個監(jiān)聽,一個監(jiān)聽是當(dāng)我們runloop即將進(jìn)入的時候(也或是從休眠狀態(tài)進(jìn)入的時候),就會 創(chuàng)建一個自動釋放池,這個優(yōu)先級最高。當(dāng)要休眠的時候,其也會有一個監(jiān)聽的函數(shù),監(jiān)聽runloop休眠的時候,也就是等runloop里任務(wù)完成后,也就是一次循環(huán)結(jié)束之前就會傾倒里面的釋放池,銷毀里面的對象和變量,這個優(yōu)先級別是最低的,這個是因為其要等runloop里其他事件都完成后才進(jìn)行此操作。

通過上面的解釋也就知道了為什么我們寫在主線程里自己創(chuàng)建的autoreleasepool外面的程序也會被添加到自動釋放池里。
上面我們是寫在主線程中的,那如果我們寫在子線程中,其會被加入到autoreleasepool中嗎?下面我們看一個官方文檔的介紹:

上面也就是說,當(dāng)放在子線程中時候,其也會放到子線程的autoreleasepool中,和主線程中的一致。
總結(jié):子線程的autoreleasepool什么時候銷毀,其是在子線程銷毀的時候會釋放其autoreleasepool,這個是在子線程中沒有開啟runloop的時候。 如果子線程開啟了runloop也就和我們之前說的主線程那個一樣了。
autoreleasepool對象在ARC中什么時候釋放總結(jié)分兩種情況
1.手動添加自動釋放池里的對象,對象是出了autoreleasepool作用域就會釋放
2.系統(tǒng)自動添加到釋放池的autoreleasepool對象,這種情況下也要分兩種,
1)如果在主線程中或者是在子線程中開啟了runloop那就在runloop即將休眠的時候進(jìn)行釋放掉(如界面不動時候runloop也就會休眠)
2)如果在子線程中沒有開啟runloop,那就在子線程銷毀的時候被釋放。
下面我們探討下一個hotPage的大小
一個hotPage的大小是4k,如下圖1左移動12位就位4096,也就是4k

那我們?nèi)绾悟炞C呢,4096 - 56(本身大?。? 8(哨兵對象) 為 4032。 4032/8 一個hotpage可存504個對象。下面就是對應(yīng)的驗證,我們創(chuàng)建505個對象,就會發(fā)現(xiàn)在505的地方會再開啟一個新page,當(dāng)前的page后面會顯示hot,而前面放了504個對象的page就會顯示cold。如果我們把505改成1009,大家想想會存多少,和開啟幾個page,其實會開啟兩個hotpage,第一個存504個對象,第二個存505個對象,這個是因為一個autorelease只有一個哨兵對象(占8字節(jié)),所以第二個hotpage就可以存505個對象。

上面這些操作都是自動釋放池push操作,那自動釋放池pop操作又是做了些啥呢,我們下面看到再pop時候傳了一個參數(shù),其實這個參數(shù)就是之前push進(jìn)來的哨兵對象。

下面的token就是傳進(jìn)來的哨兵對象,哨兵對象是放在第一位的,通過這個哨兵對象找到哨兵對象所在的page。


下面的popPage里releaseUntil是進(jìn)行釋放,釋放到哨兵對象為止,會從最后面的hot頁往前面找,找到哨兵對象所在的page為止,然后調(diào)用objc_release(obj)進(jìn)行釋放。

那自動釋放池我們可以用來做什么呢,可以防止內(nèi)存短時間內(nèi)爆漲
其是一個內(nèi)存自動回收機制,如下,當(dāng)我們把autoreleasepool去掉的時候,其內(nèi)存會爆漲。

當(dāng)我們一同autoreleasepool去包裹的時候,其內(nèi)存變化就會很小,因為得到了及時的釋放
