前言: 做爲 iOS 開發者,在面試過程當中常常會碰到這樣一個問題:在 ARC 環境下,autorelease 對象在何時釋放?這也是 iOS 內存管理的重要知識點,本文將針對這道面試題,講解 autorelease 和 @autoreleasepool。html
蘋果在 iOS5 中引入了ARC(Automatic Reference Counting)
自動引用計數,經過LLVM
編譯器和Runtime
協做來進行自動管理內存。LLVM
編譯器會在編譯時在合適的地方爲 OC 對象插入retain
、release
和autorelease
代碼,省去了在MRC(Manual Reference Counting)
手動引用計數下手動插入這些代碼的工做,減輕了開發者的工做量。面試
在MRC
下,當咱們不須要一個對象的時候,要調用release
或autorelease
方法來釋放它。調用release
會當即讓對象的引用計數減1,若是此時對象的引用計數爲0,就會當即釋放該對象的內存空間。調用autorelease
會將該對象添加進自動釋放池中,它會在一個恰當的時刻自動給對象調用release
,因此autorelease
至關於延遲了對象的釋放。網絡
官方文檔數據結構
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create 「local」 autorelease pools to help to minimize the peak memory footprint.app
以上是蘋果對自動釋放池的一段介紹,其意思爲:在事件循環(RunLoop
)的每次循環開始時,在主線程建立一個自動釋放池,並在每次循環結束時銷燬,在銷燬時釋放自動釋放池中的全部autorelease
對象。一般狀況下咱們不須要手動建立自動釋放池,可是若是咱們在循環中建立了不少臨時的autorelease
對象,則手動建立自動釋放池來管理這些對象能夠很大程度地減小內存峯值。框架
MRC
下使用NSAutoreleasePool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
複製代碼
ARC
下使用@autoreleasepool
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
複製代碼
下面咱們先經過macOS
工程來分析@autoreleasepool
的底層原理。 macOS
工程中的main()
函數什麼都沒作,只是放了一個@autoreleasepool
。less
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
複製代碼
經過 Clang clang -rewrite-objc main.m
將以上代碼轉換爲 C++ 代碼。ide
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool; }
return 0;
}
複製代碼
能夠看到:函數
@autoreleasepool
底層是建立了一個__AtAutoreleasePool
結構體對象;__AtAutoreleasePool
結構體時會在構造函數中調用objc_autoreleasePoolPush()
函數,並返回一個atautoreleasepoolobj
(POOL_BOUNDARY
存放的內存地址,下面會講到);__AtAutoreleasePool
結構體時會在析構函數中調用objc_autoreleasePoolPop()
函數,並將atautoreleasepoolobj
傳入。下面咱們進入Runtime objc4
源碼查看以上提到的兩個函數的實現。工具
備註: 本文使用的是
objc4-756.2
源碼進行分析。
// NSObject.mm
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
複製代碼
能夠得知,objc_autoreleasePoolPush()
和objc_autoreleasePoolPop()
兩個函數實際上是調用了AutoreleasePoolPage
類的兩個類方法push()
和pop()
。因此@autoreleasepool
底層就是使用AutoreleasePoolPage
類來實現的。
下面咱們來看一下AutoreleasePoolPage
類的定義:
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1) // EMPTY_POOL_PLACEHOLDER:表示一個空自動釋放池的佔位符
# define POOL_BOUNDARY nil // POOL_BOUNDARY:哨兵對象
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 用來標記已釋放的對象
static size_t const SIZE = // 每一個 Page 對象佔用 4096 個字節內存
#if PROTECT_AUTORELEASEPOOL // PAGE_MAX_SIZE = 4096
PAGE_MAX_SIZE; // must be muliple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id); // Page 的個數
magic_t const magic; // 用來校驗 Page 的結構是否完整
id *next; // 指向下一個可存放 autorelease 對象地址的位置,初始化指向 begin()
pthread_t const thread; // 指向當前線程
AutoreleasePoolPage * const parent; // 指向父結點,首結點的 parent 爲 nil
AutoreleasePoolPage *child; // 指向子結點,尾結點的 child 爲 nil
uint32_t const depth; // Page 的深度,從 0 開始遞增
uint32_t hiwat;
......
}
複製代碼
整個程序運行過程當中,可能會有多個AutoreleasePoolPage
對象。從它的定義能夠得知:
AutoreleasePoolPage
對象)是以棧
爲結點經過雙向鏈表
的形式組合而成;AutoreleasePoolPage
對象佔用4096
字節內存,其中56
個字節用來存放它內部的成員變量,剩下的空間(4040
個字節)用來存放autorelease
對象的地址。其內存分佈圖以下:
下面咱們經過源碼來分析push()
、pop()
以及autorelease
方法的實現。
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 出錯時進入調試狀態
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY); // 傳入 POOL_BOUNDARY 哨兵對象
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
複製代碼
當建立一個自動釋放池時,會調用push()
方法。push()
方法中調用了autoreleaseFast()
方法並傳入了POOL_BOUNDARY
哨兵對象。
這裏對POOL_BOUNDARY
作一下介紹:
POOL_BOUNDARY
的前世叫作POOL_SENTINEL
,稱爲哨兵對象或者邊界對象;POOL_BOUNDARY
用來區分不一樣的自動釋放池,以解決自動釋放池嵌套的問題;push()
方法將一個POOL_BOUNDARY
入棧,並返回其存放的內存地址;autorelease
對象時,將autorelease
對象的內存地址入棧,它們前面至少有一個POOL_BOUNDARY
;pop()
方法並傳入一個POOL_BOUNDARY
,會從自動釋放池中最後一個對象開始,依次給它們發送release
消息,直到遇到這個POOL_BOUNDARY
。下面咱們來看一下autoreleaseFast()
方法的實現:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 新建立的未滿的 Page
if (page && !page->full()) { // 若是當前 Page 存在且未滿
return page->add(obj); // 將 autorelease 對象入棧,即添加到當前 Page 中;
} else if (page) { // 若是當前 Page 存在但已滿
return autoreleaseFullPage(obj, page); // 建立一個新的 Page,並將 autorelease 對象添加進去
} else { // 若是當前 Page 不存在,即還沒建立過 Page
return autoreleaseNoPage(obj); // 建立第一個 Page,並將 autorelease 對象添加進去
}
}
複製代碼
autoreleaseFast()
中先是調用了hotPage()
方法得到未滿的Page
,從AutoreleasePoolPage
類的定義可知,每一個Page
的內存大小爲4096
個字節,每當Page
滿了的時候,就會建立一個新的Page
。hotPage()
方法就是用來得到這個新建立的未滿的Page
。 autoreleaseFast()
在執行過程當中有三種狀況:
Page
存在且未滿時,經過page->add(obj)
將autorelease
對象入棧,即添加到當前Page
中;Page
存在但已滿時,經過autoreleaseFullPage(obj, page)
建立一個新的Page
,並將autorelease
對象添加進去;Page
不存在,即還沒建立過Page
,經過autoreleaseNoPage(obj)
建立第一個Page
,並將autorelease
對象添加進去。下面咱們來看一下以上提到的三個方法的實現:
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
複製代碼
page->add(obj)
其實就是將autorelease
對象添加到Page
中的next
指針所指向的位置,並將next
指針指向這個對象的下一個位置,而後將該對象的位置返回。
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 {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
複製代碼
autoreleaseFullPage()
方法中經過while
循環,經過Page
的child
指針找到最後一個Page
。
Page
未滿,就經過page->add(obj)
將autorelease
對象添加到最後一個Page
中;Page
已滿,就建立一個新的Page
並經過page->add(obj)
將autorelease
對象添加進去,並將該Page
設置爲hotPage
。static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
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",
pthread_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();
}
// We are pushing an object or a non-placeholder'd pool.
// 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);
}
複製代碼
autoreleaseNoPage()
方法中會建立第一個Page
。該方法會判斷是否有空的自動釋放池存在,若是沒有會經過setEmptyPoolPlaceholder()
生成一個佔位符,表示一個空的自動釋放池。接着建立第一個Page
,設置它爲hotPage
。最後將一個POOL_BOUNDARY
添加進Page
中,並返回POOL_BOUNDARY
的下一個位置。
小結: 以上就是
push
操做的實現,往自動釋放池中添加一個POOL_BOUNDARY
,並返回它存放的內存地址。接着每有一個對象調用autorelease
方法,會將它的內存地址添加進自動釋放池中。
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()
方法添加進Page
中。
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();
}
}
}
複製代碼
pop()
方法的傳參token
即爲POOL_BOUNDARY
對應在Page
中的地址。當銷燬自動釋放池時,會調用pop()
方法將自動釋放池中的autorelease
對象所有釋放(其實是從自動釋放池的中的最後一個入棧的autorelease
對象開始,依次給它們發送一條release
消息,直到遇到這個POOL_BOUNDARY
)。pop()
方法的執行過程以下:
token
是否是EMPTY_POOL_PLACEHOLDER
,是的話就清空這個自動釋放池;pageForPointer(token)
拿到token
所在的Page
(自動釋放池的首個Page
);page->releaseUntil(stop)
將自動釋放池中的autorelease
對象所有釋放,傳參stop
即爲POOL_BOUNDARY
的地址;Page
是否有子Page
,有的話就銷燬。pop()
方法中釋放autorelease
對象的過程在releaseUntil()
方法中,下面來看一下這個方法的實現:
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; // next指針是指向最後一個對象的後一個位置,因此須要先減1
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
複製代碼
releaseUntil()
方法其實就是經過一個while
循環,從最後一個入棧的autorelease
對象開始,依次給它們發送一條release
消息,直到遇到這個POOL_BOUNDARY
。
咱們來看一下建立一個Page
的過程。AutoreleasePoolPage()
方法的參數爲parentPage
,新建立的Page
的depth
加一,next
指針的初始位置指向begin
,將新建立的Page
的parent
指針指向parentPage
。將parentPage
的child
指針指向本身,這就造成了雙向鏈表
的結構。
AutoreleasePoolPage(AutoreleasePoolPage *newParent)
: magic(), next(begin()), thread(pthread_self()),
parent(newParent), child(nil),
depth(parent ? 1+parent->depth : 0),
hiwat(parent ? parent->hiwat : 0)
{
if (parent) {
parent->check();
assert(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
複製代碼
下面再來看一下begin
、end
、empty
、full
這些方法的實現。
begin
的地址爲:Page
本身的地址+Page
對象的大小56
個字節;end
的地址爲:Page
本身的地址+4096
個字節;empty
判斷Page
是否爲空的條件是next
地址是否是等於begin
;full
判斷Page
是否已滿的條件是next
地址是否是等於end
(棧頂)。id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
複製代碼
小結:
push
操做是往自動釋放池中添加一個POOL_BOUNDARY
,並返回它存放的內存地址;- 接着每有一個對象調用
autorelease
方法,會將它的內存地址添加進自動釋放池中。pop
操做是傳入一個POOL_BOUNDARY
的內存地址,從最後一個入棧的autorelease
對象開始,將自動釋放池中的autorelease
對象所有釋放(其實是給它們發送一條release
消息),直到遇到這個POOL_BOUNDARY
。
能夠經過如下私有函數來查看自動釋放池的狀況:
extern void _objc_autoreleasePoolPrint(void);
複製代碼
接下來咱們經過macOS
工程代碼示例,結合AutoreleasePoolPage
的內存分佈圖以及_objc_autoreleasePoolPrint()
私有函數,來幫助咱們更好地理解@autoreleasepool
的原理。
注意: 因爲
ARC
環境下不能調用autorelease
等方法,因此須要將工程切換爲MRC
環境。
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool {
_objc_autoreleasePoolPrint(); // print2
HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
}
_objc_autoreleasePoolPrint(); // print4
return 0;
}
複製代碼
// 自動釋放池的狀況
objc[68122]: ############## (print1)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 0 releases pending. //當前自動釋放池中沒有任何對象
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: ##############
objc[68122]: ############## (print2)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 1 releases pending. //當前自動釋放池中有1個對象,這個對象爲POOL_BOUNDARY
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68122]: ##############
objc[68122]: ############## (print3)
objc[68122]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68122]: 3 releases pending. //當前自動釋放池中有3個對象
objc[68122]: [0x102802000] ................ PAGE (hot) (cold)
objc[68122]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68122]: [0x102802040] 0x100704a10 HTPerson //p1
objc[68122]: [0x102802048] 0x10075cc30 HTPerson //p2
objc[68122]: ##############
objc[68156]: ############## (print4)
objc[68156]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68156]: 0 releases pending. //當前自動釋放池中沒有任何對象,由於@autoreleasepool做用域結束,調用pop方法釋放了對象
objc[68156]: [0x100810000] ................ PAGE (hot) (cold)
objc[68156]: ##############
複製代碼
int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { //r1 = push()
_objc_autoreleasePoolPrint(); // print2
HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
@autoreleasepool { //r2 = push()
HTPerson *p3 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print4
@autoreleasepool { //r3 = push()
HTPerson *p4 = [[[HTPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print5
} //pop(r3)
_objc_autoreleasePoolPrint(); // print6
} //pop(r2)
_objc_autoreleasePoolPrint(); // print7
} //pop(r1)
_objc_autoreleasePoolPrint(); // print8
return 0;
}
複製代碼
// 自動釋放池的狀況
objc[68285]: ############## (print1)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 0 releases pending. //當前自動釋放池中沒有任何對象
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: ##############
objc[68285]: ############## (print2)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 1 releases pending. //當前自動釋放池中有1個對象
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: ##############
objc[68285]: ############## (print3)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 3 releases pending. //當前自動釋放池中有3個對象(1個@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: ##############
objc[68285]: ############## (print4)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 5 releases pending. //當前自動釋放池中有5個對象(2個@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: [0x102802050] ################ POOL 0x102802050 //POOL_BOUNDARY
objc[68285]: [0x102802058] 0x1005065b0 HTPerson //p3
objc[68285]: ##############
objc[68285]: ############## (print5)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 7 releases pending. //當前自動釋放池中有7個對象(3個@autoreleasepool)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038 //POOL_BOUNDARY
objc[68285]: [0x102802040] 0x100707d80 HTPerson //p1
objc[68285]: [0x102802048] 0x100707de0 HTPerson //p2
objc[68285]: [0x102802050] ################ POOL 0x102802050 //POOL_BOUNDARY
objc[68285]: [0x102802058] 0x1005065b0 HTPerson //p3
objc[68285]: [0x102802060] ################ POOL 0x102802060 //POOL_BOUNDARY
objc[68285]: [0x102802068] 0x100551880 HTPerson //p4
objc[68285]: ##############
objc[68285]: ############## (print6)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 5 releases pending. //當前自動釋放池中有5個對象(第3個@autoreleasepool已釋放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038
objc[68285]: [0x102802040] 0x100707d80 HTPerson
objc[68285]: [0x102802048] 0x100707de0 HTPerson
objc[68285]: [0x102802050] ################ POOL 0x102802050
objc[68285]: [0x102802058] 0x1005065b0 HTPerson
objc[68285]: ##############
objc[68285]: ############## (print7)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 3 releases pending. //當前自動釋放池中有3個對象(第二、3個@autoreleasepool已釋放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: [0x102802038] ################ POOL 0x102802038
objc[68285]: [0x102802040] 0x100707d80 HTPerson
objc[68285]: [0x102802048] 0x100707de0 HTPerson
objc[68285]: ##############
objc[68285]: ############## (print8)
objc[68285]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[68285]: 0 releases pending. //當前自動釋放池沒有任何對象(3個@autoreleasepool都已釋放)
objc[68285]: [0x102802000] ................ PAGE (hot) (cold)
objc[68285]: ##############
複製代碼
由AutoreleasePoolPage
類的定義可知,自動釋放池(即全部的AutoreleasePoolPage
對象)是以棧
爲結點經過雙向鏈表
的形式組合而成。每當Page
滿了的時候,就會建立一個新的Page
,並設置它爲hotPage
,而首個Page
爲coldPage
。接下來咱們來看一下多個Page
和多個@autoreleasepool
嵌套的狀況。
int main(int argc, const char * argv[]) {
@autoreleasepool { //r1 = push()
for (int i = 0; i < 600; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
@autoreleasepool { //r2 = push()
for (int i = 0; i < 500; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
@autoreleasepool { //r3 = push()
for (int i = 0; i < 200; i++) {
HTPerson *p = [[[HTPerson alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
} //pop(r3)
} //pop(r2)
} //pop(r1)
return 0;
}
複製代碼
一個AutoreleasePoolPage
對象的內存大小爲4096
個字節,它自身成員變量佔用內存56
個字節,因此剩下的4040
個字節用來存儲autorelease
對象的內存地址。又由於64bit
下一個OC
對象的指針所佔內存爲8
個字節,因此一個Page
能夠存放505
個對象的地址。POOL_BOUNDARY
也是一個對象,由於它的值爲nil
。因此以上代碼的自動釋放池內存分佈圖以下所示。
objc[69731]: ##############
objc[69731]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[69731]: 1303 releases pending. //當前自動釋放池中有1303個對象(3個POOL_BOUNDARY和1300個HTPerson實例)
objc[69731]: [0x100806000] ................ PAGE (full) (cold) /* 第一個PAGE,full表明已滿,cold表明coldPage */
objc[69731]: [0x100806038] ################ POOL 0x100806038 //POOL_BOUNDARY
objc[69731]: [0x100806040] 0x10182a040 HTPerson //p1
objc[69731]: [0x100806048] ..................... //...
objc[69731]: [0x100806ff8] 0x101824e40 HTPerson //p504
objc[69731]: [0x102806000] ................ PAGE (full) /* 第二個PAGE */
objc[69731]: [0x102806038] 0x101824e50 HTPerson //p505
objc[69731]: [0x102806040] ..................... //...
objc[69731]: [0x102806330] 0x101825440 HTPerson //p600
objc[69731]: [0x102806338] ################ POOL 0x102806338 //POOL_BOUNDARY
objc[69731]: [0x102806340] 0x101825450 HTPerson //p601
objc[69731]: [0x102806348] ..................... //...
objc[69731]: [0x1028067e0] 0x101825d90 HTPerson //p1008
objc[69731]: [0x102804000] ................ PAGE (hot) /* 第三個PAGE,hot表明hotPage */
objc[69731]: [0x102804038] 0x101826dd0 HTPerson //p1009
objc[69731]: [0x102804040] ..................... //...
objc[69731]: [0x102804310] 0x101827380 HTPerson //p1100
objc[69731]: [0x102804318] ################ POOL 0x102804318 //POOL_BOUNDARY
objc[69731]: [0x102804320] 0x101827390 HTPerson //p1101
objc[69731]: [0x102804328] ..................... //...
objc[69731]: [0x102804958] 0x10182b160 HTPerson //p1300
objc[69731]: ##############
複製代碼
從以上macOS
工程示例能夠得知,在@autoreleasepool
大括號結束的時候,就會調用Page
的pop()
方法,給@autoreleasepool
中的autorelease
對象發送release
消息。
那麼在iOS
工程中,方法裏的autorelease
對象是何時釋放的呢?有系統干預釋放和手動干預釋放兩種狀況。系統干預釋放是不指定@autoreleasepool
,全部autorelease
對象都由main
函數中的@autoreleasepool
管理。手動干預釋放就是將autorelease
對象添加進咱們手動建立的@autoreleasepool
。
下面仍是在MRC
環境下進行分析。
在iOS
工程的main()
函數中也有一個@autoreleasepool
,這個@autoreleasepool
負責了應用程序全部autorelease
對象的釋放。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
- (void)viewDidLoad {
[super viewDidLoad];
HTPerson *person = [[[HTPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[HTPerson dealloc]
// -[ViewController viewDidAppear:]
複製代碼
能夠看到,調用了autorelease
方法的person
對象不是在viewDidLoad
方法結束後釋放,而是在viewWillAppear
方法結束後釋放,說明在viewWillAppear
方法結束的時候,調用了pop()
方法釋放了person
對象。其實這是由RunLoop
控制的,下面來說解一下RunLoop
和@autoreleasepool
的關係。
學習這個知識點以前,須要先搞懂
RunLoop
的事件循環機制以及它的6
種活動狀態,能夠查看個人文章:
《深刻淺出 RunLoop(二):數據結構》
《深刻淺出 RunLoop(三):事件循環機制》
iOS
在主線程的RunLoop
中註冊了兩個Observer
。
Observer
監聽了kCFRunLoopEntry
事件,會調用objc_autoreleasePoolPush()
;Observer
kCFRunLoopBeforeWaiting
事件,會調用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
;kCFRunLoopBeforeExit
事件,會調用objc_autoreleasePoolPop()
。因此,在iOS
工程中系統干預釋放的autorelease
對象的釋放時機是由RunLoop
控制的,會在當前RunLoop
每次循環結束時釋放。以上person
對象在viewWillAppear
方法結束後釋放,說明viewDidLoad
和viewWillAppear
方法在同一次循環裏。
kCFRunLoopEntry
:在即將進入RunLoop
時,會自動建立了一個__AtAutoreleasePool
結構體對象,並調用objc_autoreleasePoolPush()
函數。kCFRunLoopBeforeWaiting
:在RunLoop
即將休眠時,會自動銷燬一個__AtAutoreleasePool
對象,調用objc_autoreleasePoolPop()
。而後建立一個新的__AtAutoreleasePool
對象,並調用objc_autoreleasePoolPush()
。kCFRunLoopBeforeExit
,在即將退出RunLoop
時,會自動銷燬最後一個建立的__AtAutoreleasePool
對象,並調用objc_autoreleasePoolPop()
。咱們再來看一下手動干預釋放的狀況。
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
HTPerson *person = [[[HTPerson alloc] init] autorelease];
}
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
// -[HTPerson dealloc]
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[ViewController viewDidAppear:]
複製代碼
能夠看到,添加進手動指定的@autoreleasepool
中的autorelease
對象,在@autoreleasepool
大括號結束時就會釋放,不受RunLoop
控制。
以上都是在MRC
環境下分析,由於ARC
下不能給對象調用retain
release
、autorelease
等方法。
那ARC
中方法裏的局部對象何時釋放?其實只要知道LLVM
編譯器在編譯時給對象插入release
仍是autorelease
方法就知道了。
alloc
/new
/copy
/mutableCopy
方法建立的對象,LLVM
編譯器在編譯時會給對象插入release
方法,因此這類局部對象,在方法結束時就會釋放。LLVM
編譯器在編譯時會給對象插入autorelease
方法,因此這類對象的釋放時機由RunLoop
控制。回到咱們最初的面試題,在ARC
環境下,autorelease
對象在何時釋放?咱們就分系統干預釋放
和手動干預釋放
兩種狀況回答。
iOS
工程在ARC
環境下,main
函數中的@autoreleasepool
負責了應用程序全部autorelease
對象的釋放。一般狀況下咱們不須要手動添加@autoreleasepool
,可是若是咱們須要在循環中建立了不少臨時的autorelease
對象,則手動添加@autoreleasepool
來管理這些對象能夠很大程度地減小內存峯值。好比在for
循環中alloc
圖片數據等內存消耗較大的場景,須要手動添加@autoreleasepool
。
蘋果給出了三種須要手動添加
@autoreleasepool
的狀況:
- ① 若是你編寫的程序不是基於 UI 框架的,好比說命令行工具;
- ② 若是你編寫的循環中建立了大量的臨時對象;
你能夠在循環內使用@autoreleasepool
在下一次迭代以前處理這些對象。在循環中使用@autoreleasepool
有助於減小應用程序的最大內存佔用。- ③ 若是你建立了輔助線程。
一旦線程開始執行,就必須建立本身的@autoreleasepool
;不然,你的應用程序將存在內存泄漏。(有關詳細信息,請參見自動釋放池塊和線程。)
更多關於@autoreleasepool
的使用能夠查看蘋果官方文檔《Advanced Memory Management Programming Guide》。