有關內存管理的相關優化方案和引用計數的相關原理,咱們已經瞭解,本章來說解在內存管理中的另外一個方案Autoreleasepoolbash
傳送門☞iOS底層學習 - 內存管理以內存管理方案app
傳送門☞iOS底層學習 - 內存管理之weak原理探究框架
經過以前章節的學習,咱們知道在ARC
下,LLVM編譯器會自動幫咱們生產retain
、release
和autorelease
等代碼,減小了在MRC
下的工做量。調用autorelease
會將該對象添加進自動釋放池中,它會在一個恰當的時刻自動給對象調用release
,因此autorelease
至關於延遲了對象的釋放。less
可是在ARC
下,autorelease
方法已被禁用,咱們可使用__autoreleasing
修飾符修飾對象將對象註冊到自動釋放池中。ide
MRC
下,可使用NSAutoreleasePool
或者@autoreleasepool
。建議使用@autoreleasepool
,蘋果說它比NSAutoreleasePool
快大約六倍。NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
複製代碼
NSAutoreleasePool
類建立自動釋放池,只能使用@autoreleasepool。
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
複製代碼
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.函數
以上是蘋果對自動釋放池的一段介紹,其意思爲:AppKit
和 UIKit
框架在事件循環(RunLoop
)的每次循環開始時,在主線程建立一個自動釋放池,並在每次循環結束時銷燬它,在銷燬時釋放自動釋放池中的全部autorelease
對象。一般狀況下咱們不須要手動建立自動釋放池,可是若是咱們在循環中建立了不少臨時的autorelease
對象,則手動建立自動釋放池來管理這些對象能夠很大程度地減小內存峯值。工具
咱們知道在main
函數中,會建立一個@autoreleasepool {}
對象,那麼其底層的結構是怎樣的呢?oop
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
複製代碼
咱們仍是使用clang -rewrite-objc main.m
命令,轉換爲C++代碼查看。經過如下代碼,咱們能夠發現轉換後@autoreleasepool
主要作了如下幾點:post
@autoreleasepool
底層是建立了一個__AtAutoreleasePool
結構體對象;__AtAutoreleasePool
結構體時會在構造函數中調用objc_autoreleasePoolPush()
函數,並返回一個atautoreleasepoolobj
(POOL_BOUNDARY存放的內存地址,下面會講到);__AtAutoreleasePool
結構體時會在析構函數中調用objc_autoreleasePoolPop()
函數,並將atautoreleasepoolobj
傳入。struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; }
return 0;
}
複製代碼
首先來看AutoreleasePoolPage
的相關源碼,其幾個成員變量的含義以下:學習
AutoreleasePoolPage
的結構是否完整。AutoreleasePoolPage
中下一個爲空的內存地址(新來的對象會存儲到next處),初始化時指向begin()。AutoreleasePoolPage
屬於一個線程,一個線程中能夠有多個AutoreleasePoolPage
)。PAGE_MAX_SIZE
,4096個字節,其中56
個字節用來存儲本身的變量,剩下的4040
個字節用來存儲要釋放的對象,也就是最多505
個對象。POOL_SENTINEL
,稱爲哨兵對象或者邊界對象;POOL_BOUNDARY
用來區分不一樣的自動釋放池,以解決自動釋放池嵌套的問題push()
方法將一個POOL_BOUNDARY
入棧,並返回其存放的內存地址;autorelease
對象時,將autorelease
對象的內存地址入棧,它們前面至少有一個POOL_BOUNDARY
;pop()
方法並傳入一個POOL_BOUNDARY
,會從自動釋放池中最後一個對象開始,依次給它們發送release
消息,直到遇到這個POOL_BOUNDARY
。class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
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)
{
}
};
----------------------------------------------------------------------------------
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MIN_SIZE; // size and alignment, power of 2
#endif
private:
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const COUNT = SIZE / sizeof(id);
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
// SIZE-sizeof(*this) bytes of contents follow
......
}
複製代碼
/***********************************************************************
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.
**********************************************************************/
翻譯以下
一個線程的自動釋放池是一個指針的堆棧結構。
每一個指針表明一個須要釋放的對象或者POOL_BOUNDARY(自動釋放池邊界)
一個 pool token 就是這個 pool 所對應的 POOL_BOUNDARY 的內存地址。當這個 pool 被 pop 的時候,全部內存地址在 pool token 以後的對象都會被 release。 這個堆棧被劃分紅了一個以 page 爲結點的雙向鏈表。pages 會在必要的時候動態地增長或刪除。
Thread-local storage(線程局部存儲)指向 hot page ,即最新添加的 autoreleased 對象所在的那個 page 。
經過上面對成員變量的解析和上方官方的註釋,咱們能夠知道AutoreleasePoolPage
底層結構以下:
AutoreleasePoolPage
是以棧爲結點經過雙向鏈表的形式組合而成;遵循先進後出規則,整個自動釋放池由一系列的AutoreleasePoolPage
組成的,而AutoreleasePoolPage
是以雙向鏈表的形式鏈接起來。AutoreleasePoolPage
對象佔用4096
字節內存,其中56
個字節用來存放它內部的成員變量,剩下的空間(4040個字節)用來存放autorelease對象的地址。要注意的是第一頁只有504個對象,由於在建立page的時候會在next
的位置插入1個POOL_SENTINEL
。POOL_BOUNDARY
爲哨兵對象,入棧時插入,出棧時釋放對象到此傳入的哨兵對象該圖表示AutoreleasePoolPage
的雙向列表結構
AutoreleasePoolPage
的雙向列表和棧結構
首先咱們看objc_autoreleasePoolPush
的源碼,發現其內部就是調用了AutoreleasePoolPage
的push()
方法。
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
複製代碼
來到AutoreleasePoolPage
內部的push()
方法,其中slowpath
表示不多會走到,是底部的容錯處理,因此最終會走到autoreleaseFast
方法中
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
源碼,先是調用了hotPage()
,hotPage()
方法就是用來得到新建立的未滿的Page。其內部主要是判斷邏輯:
page->add(obj)
將 autorelease 對象入棧,即添加到當前 Page 中autoreleaseFullPage
,建立一個新的 Page,並將 autorelease 對象添加進去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 對象添加進去
}
}
複製代碼
首先咱們來看一下,如何判斷當前page是不是滿狀態的。
begin
的地址爲:Page本身的地址+Page對象的大小56個字節;end
的地址爲:Page本身的地址+4096個字節;empty
:判斷Page是否爲空的條件是next地址是否是等於begin;full
:判斷Page是否已滿的條件是next地址是否是等於end(棧頂)。咱們知道next
指向的是下一個AutoreleasePoolPage
中下一個爲空的內存地址,新對象會存在next
,若是此時next
指向end
則表明當前AutoreleasePoolPage
已滿。
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();
}
複製代碼
當page沒有存滿時,會調用此方法,內部的原理很是簡單,就是一個壓棧的操做,並將next
指針指向這個對象的下一個位置,而後將該對象的位置返回。
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
複製代碼
若是當前 Page 存在但已滿,會調用此方法。其內部實現的主要方法就是一個do..while
循環,主要實現了一下的邏輯
page->child
page->full()
new
一個AutoreleasePoolPage
setHotPage(page)
將當前頁設置爲活躍page->add
壓棧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);
}
複製代碼
當沒有page時,會走到此方法,其主要邏輯以下:
setEmptyPoolPlaceholder()
生成一個佔位符,表示一個空的自動釋放池Page
,設置它爲hotPage
POOL_BOUNDARY
添加進Page中,並返回POOL_BOUNDARY
的下一個位置。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",
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();
}
// 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);
}
複製代碼
看完對象入棧的實現,咱們再來看一下出棧的實現。
首先pop的入參token
即爲POOL_BOUNDARY
對應在Page中的地址。當銷燬自動釋放池時,會從從自動釋放池的中的最後一個入棧的autorelease對象開始,依次給它們發送一條release
消息,直到遇到這個POOL_BOUNDARY
,具體的步驟以下:
token
是否是EMPTY_POOL_PLACEHOLDER
,是的話就清空這個自動釋放池pageForPointer(token)
拿到token
所在的Page
page->releaseUntil(stop)
將自動釋放池中的autorelease
對象所有釋放,傳參sto
p即爲POOL_BOUNDARY
的地址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();
}
}
}
複製代碼
該方法,主要是經過token來拿到當前所在的page。主要實現原理是將指針token與頁面的大小(4096)取模,能夠獲得當前指針的偏移量。而後將指針的地址減偏移量即可以獲得首地址。即該page的地址
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;
}
複製代碼
pop()方法中釋放autorelease對象的過程在releaseUntil()方法中,下面來看一下這個方法的實現:
releaseUntil()
方法其實就是經過一個while
循環hotPage
開始,一直釋放,直到stop
,即傳入的POOL_BOUNDARY
hotPage
void releaseUntil(id *stop)
{
// Not recursive: we donot 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 canot 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
}
複製代碼
kill
方法刪除雙向鏈表中的每個的page,找到當前page
的 child
方向尾部 page
,而後反向挨着釋放而且把其parent
節點的 child
指針置空。
void kill()
{
// Not recursive: we donot 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;
// 循環刪除每個page
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
複製代碼
準備:
extern void _objc_autoreleasePoolPrint(void);
方法來打印autoreleasePool的相關信息int main(int argc, const char * argv[]) {
_objc_autoreleasePoolPrint(); // print1
@autoreleasepool { //r1 = push()
_objc_autoreleasePoolPrint(); // print2
NSObject *p1 = [[[NSObject alloc] init] autorelease];
NSObject *p2 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print3
@autoreleasepool { //r2 = push()
NSObject *p3 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print4
@autoreleasepool { //r3 = push()
NSObject *p4 = [[[NSObject alloc] init] autorelease];
_objc_autoreleasePoolPrint(); // print5
} //pop(r3)
_objc_autoreleasePoolPrint(); // print6
} //pop(r2)
_objc_autoreleasePoolPrint(); // print7
} //pop(r1)
_objc_autoreleasePoolPrint(); // print8
return 0;
}
複製代碼
打印結果過以下,經過打印結果,咱們能夠印證上面原理的探索,其主要的進出棧流程以下圖所示,且做用域只在@autoreleasepool {}
之間,超過以後就所有調用pop釋放
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: [0x1] ................ PAGE (placeholder)
objc[12943]: [0x1] ################ POOL (placeholder)
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 3 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 5 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: [0x7f924480b050] ################ POOL 0x7f924480b050
objc[12943]: [0x7f924480b058] 0x600001b34090 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 7 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: [0x7f924480b050] ################ POOL 0x7f924480b050
objc[12943]: [0x7f924480b058] 0x600001b34090 NSObject
objc[12943]: [0x7f924480b060] ################ POOL 0x7f924480b060
objc[12943]: [0x7f924480b068] 0x600001b2c030 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 5 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: [0x7f924480b050] ################ POOL 0x7f924480b050
objc[12943]: [0x7f924480b058] 0x600001b34090 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 3 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: [0x7f924480b038] ################ POOL 0x7f924480b038
objc[12943]: [0x7f924480b040] 0x600001b34070 NSObject
objc[12943]: [0x7f924480b048] 0x600001b34080 NSObject
objc[12943]: ##############
objc[12943]: ##############
objc[12943]: AUTORELEASE POOLS for thread 0x11aee5dc0
objc[12943]: 0 releases pending.
objc[12943]: [0x7f924480b000] ................ PAGE (hot) (cold)
objc[12943]: ##############
複製代碼
int main(int argc, const char * argv[]) {
@autoreleasepool { //r1 = push()
for (int i = 0; i < 600; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
@autoreleasepool { //r2 = push()
for (int i = 0; i < 500; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
@autoreleasepool { //r3 = push()
for (int i = 0; i < 200; i++) {
NSObject *p = [[[NSObject alloc] init] autorelease];
}
_objc_autoreleasePoolPrint();
} //pop(r3)
} //pop(r2)
} //pop(r1)
return 0;
}
複製代碼
能夠看到打印結果以下:根據原理的探究,咱們知道每一個page除了第一頁是504個對象外,其餘最多存儲505個對象,當一個page滿了時候,會建立一個新的page,而且每一個page之間是以棧
爲結點經過雙向鏈表
的形式組合而成。其主要流程以下圖所示
objc[69731]: ##############
objc[69731]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[69731]: 1303 releases pending. //當前自動釋放池中有1303個對象(3個POOL_BOUNDARY和1300個NSObject實例)
objc[69731]: [0x100806000] ................ PAGE (full) (cold) /* 第一個PAGE,full表明已滿,cold表明coldPage */
objc[69731]: [0x100806038] ################ POOL 0x100806038 //POOL_BOUNDARY
objc[69731]: [0x100806040] 0x10182a040 NSObject //p1
objc[69731]: [0x100806048] ..................... //...
objc[69731]: [0x100806ff8] 0x101824e40 NSObject //p504
objc[69731]: [0x102806000] ................ PAGE (full) /* 第二個PAGE */
objc[69731]: [0x102806038] 0x101824e50 NSObject //p505
objc[69731]: [0x102806040] ..................... //...
objc[69731]: [0x102806330] 0x101825440 NSObject //p600
objc[69731]: [0x102806338] ################ POOL 0x102806338 //POOL_BOUNDARY
objc[69731]: [0x102806340] 0x101825450 NSObject //p601
objc[69731]: [0x102806348] ..................... //...
objc[69731]: [0x1028067e0] 0x101825d90 NSObject //p1008
objc[69731]: [0x102804000] ................ PAGE (hot) /* 第三個PAGE,hot表明hotPage */
objc[69731]: [0x102804038] 0x101826dd0 NSObject //p1009
objc[69731]: [0x102804040] ..................... //...
objc[69731]: [0x102804310] 0x101827380 NSObject //p1100
objc[69731]: [0x102804318] ################ POOL 0x102804318 //POOL_BOUNDARY
objc[69731]: [0x102804320] 0x101827390 NSObject //p1101
objc[69731]: [0x102804328] ..................... //...
objc[69731]: [0x102804958] 0x10182b160 NSObject //p1300
objc[69731]: ##############
複製代碼
關於RunLoop的相關知識,能夠查看文章☞iOS底層學習 - 深刻RunLoop
其中主要的的RunLoop運行流程以下圖所示
並且經過打印[NSRunLoop currentRunLoop]
,能夠發現其中有_wrapRunLoopWithAutoreleasePoolHandler()
表明的相關AutoreleasePool的回調。
<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>)}}
複製代碼
那麼,RunLoop和AutoreleasePool的主要關係以下
kCFRunLoopEntry
:在即將進入RunLoop時,會自動建立一個__AtAutoreleasePool
結構體對象,並調用objc_autoreleasePoolPush()
函數。kCFRunLoopBeforeWaiting
:在RunLoop即將休眠時,會自動銷燬一個__AtAutoreleasePool
對象,調用objc_autoreleasePoolPop()
。而後建立一個新的__AtAutoreleasePool對象
,並調用objc_autoreleasePoolPush()
。kCFRunLoopBeforeExi
t,在即將退出RunLoop時,會自動銷燬最後一個建立的__AtAutoreleasePool
對象,並調用objc_autoreleasePoolPop()
。瞭解了他們之間的關係,咱們能夠經過main函數,來分析一下
// Xcode 11
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
--------------------------------------------------------------------------------
// Xcode 舊版本
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
咱們知道@autoreleasepool {}
的做用域只在其大括號之間,並且UIApplicationMain
主線程會建立主RunLoop,經過上面的探究,咱們知道在建立RunLoop的時候,也會對應的建立AutoreleasePool
。其中使用autorelease
修飾的對象都會添加到RunLoop建立的自動釋放池中。
因此Xcode 11和以前版本的區別,主要就是Xcode 11將@autoreleasepool {}
提早,這能夠保證@autoreleasepool
中的autorelease
對象在程序啓動後當即釋放。而以前的版本是在主線程RunLoop建立的自動釋放池的外層的,意味着程序結束後main函數中的@autoreleasepool
中的autorelease
對象纔會釋放。
在平時的開發中,咱們通常是不須要使用@autoreleasepool{}
的,可是如下幾種狀況可使用
@autoreleasepool
在下一次迭代以前處理這些對象。在循環中使用@autoreleasepool有助於減小應用程序的最大內存佔用。使用__autorelease
修飾的對象,會被系統自動加入RunLoop建立的自動釋放池中,隨RunLoop生命週期釋放。
release
,保證了對象的延遲釋放AutoreleasePoolPage
組成的,而AutoreleasePoolPage
是以雙向鏈表的形式鏈接起來。objc_autoreleasePoolPush()
來入棧,調用objc_autoreleasePoolPop()
來出棧POOL_BOUNDARY
哨兵對象來做爲出入棧的標誌位
push()
方法將一個POOL_BOUNDARY
入棧,並返回其存放的內存地址;autorelease對象
時,將autorelease對象的內存地址入棧,它們前面至少有一個POOL_BOUNDARY
;pop()
方法並傳入一個POOL_BOUNDARY
,會從自動釋放池中最後一個對象開始,依次給它們發送release
消息,直到遇到這個POOL_BOUNDARY
。4096
字節內存,其中56個
字節用來存放它內部的成員變量,剩下的空間(4040個字節
)用來存放autorelease對象的地址。要注意的是第一頁只有504個對象
,由於在建立page的時候會在next的位置插入1個POOL_BOUNDARY。page->add(obj)
,將 autorelease 對象入棧,並將next指針指向這個對象的下一個位置,而後將該對象的位置返回autoreleaseFullPage
,循環查找page->child並判斷是否已滿,都已滿則建立新的AutoreleasePoolPage,並將 autorelease 對象入棧,設置HotPageautoreleaseNoPage
,先會判斷是否有空的自動釋放池存在並生成佔位符,而後建立一個新page並設置HotPage,依次插入POOL_BOUNDARY和autorelease 對象入棧kCFRunLoopEntry
:在即將進入RunLoop時,會自動建立一個__AtAutoreleasePool結構體對象,並調用objc_autoreleasePoolPush()函數。kCFRunLoopBeforeWaiting
:在RunLoop即將休眠時,會自動銷燬一個__AtAutoreleasePool對象,調用objc_autoreleasePoolPop()。而後建立一個新的__AtAutoreleasePool對象,並調用objc_autoreleasePoolPush()。kCFRunLoopBeforeExit
,在即將退出RunLoop時,會自動銷燬最後一個建立的__AtAutoreleasePool對象,並調用objc_autoreleasePoolPop()。