AutoReleasePool 自動釋放池
AutoReleasePool
是OC的一種內(nèi)存自動回收機制
,它可以將加入AutoReleasePool
中變量的release時機
--- 延遲环础。
當我們創(chuàng)建一個對象
的時候溜腐,正常情況下,變量會在超出其作用域的時候立即release
。如果將對象加入到自動釋放池中,這個對象不會立即釋放
,而是等到runloop休眠 或者 超出autoreleasepool作用域{}
之后才會被釋放
展融。
- 1、從程序啟動到加載完成豫柬,主線程對應的
RunLoop
會處于休眠狀態(tài)告希,等待用戶交互來喚醒RunLoop
。 - 2烧给、用戶的每一次交互都會啟動一次
RunLoop
燕偶,用于處理用戶的所有點擊,觸摸事件等础嫡。 - 3指么、
RunLoop
在監(jiān)聽到交互事件之后,就會創(chuàng)建
自動釋放池榴鼎,并將所有延遲釋放的對象添加到自動釋放池伯诬。 - 4、在一次完整的
RunLoop
結束之前巫财,會向自動釋放池中所有的對象發(fā)送release消息
盗似,然后自動釋放池。
在大致了解自動釋放池的工作流程之后翁涤,我們一起來探索一下自動釋放池桥言。
在日常的開發(fā)中萌踱,我們見到的最多的自動釋放池就是main
函數(shù)里面的自動釋放池葵礼。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
*******
在這個@autoreleasepool Block 中号阿,只包含了一行代碼,這行代碼將所有的事件鸳粉、消息全部交給了`UIApplication`來處理扔涧。
?????? 注意:整個 iOS 的應用都是包含在一個自動釋放池 Block 中的。
下面我們?yōu)榱藴p少干擾代碼届谈,將自動釋放池中的代碼刪除枯夜,只保留自動釋放池。然后將main.m
文件轉(zhuǎn)cpp
來探索一下艰山,就像我們探索Block
一樣(Block 底層原理(一)
)
int main(int argc, char * argv[]) {
@autoreleasepool {
}
}
**************
$ clang -rewrite-objc main.m
在main.cpp
文件中我們可以看到:
struct __AtAutoreleasePool {
///構造函數(shù)
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
///析構函數(shù)
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
}
我們會發(fā)現(xiàn)湖雹,main.m
里面的@autoreleasepool {}
變成了__AtAutoreleasePool __autoreleasepool
。而__AtAutoreleasePool
又是一個結構體曙搬;所以自動釋放池是一個摔吏。
仔細觀察__AtAutoreleasePool
這個結構體,會發(fā)現(xiàn)結構體兩個函數(shù):
1纵装、構造函數(shù) objc_autoreleasePoolPush()
征讲,會在結構體初始化的時候調(diào)用;
2橡娄、析構函數(shù) objc_autoreleasePoolPop()
诗箍,還在結構體析構的時候調(diào)用(也就是說在出了作用域后,會自動調(diào)用析構
)挽唉。
看到兩個函數(shù)滤祖,不知道大家有什么想法,給我的感覺就是瓶籽,這個一定跟有關匠童,大家仔細品一品這兩個函數(shù)名。
到這里棘劣,我們好像已經(jīng)將自動釋放池的面紗揭開了一點點俏让,下面我們順著這個思路繼續(xù)探索。
源碼探索
上面我們看到了連個函數(shù)茬暇,我們在源碼中找這個連個函數(shù)是下面的樣子:
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
我們會發(fā)現(xiàn)首昔,兩個函數(shù)中,都用到了AutoreleasePoolPage
糙俗,這兩個函數(shù)就是對AutoreleasePoolPage
對應的靜態(tài)方法push
&pop
的封裝勒奇。
那么我們就再去尋找一下AutoreleasePoolPage
。
class AutoreleasePoolPage : private AutoreleasePoolPageData
{.......}
///繼續(xù)跟進
????
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)
{
}
};
-
magic
用來校驗AutoreleasePoolPage
的結構是否完整巧骚; -
next
指向最新添加的autoreleased
對象的下一個位置赊颠,初始化時指向begin()
格二; -
thread
指向當前線程; -
parent
指向父結點竣蹦,第一個結點的parent
值為nil
; -
child
指向子結點顶猜,最后一個結點的child
值為nil
; -
depth
代表深度,從 0 開始痘括,往后遞增 1长窄; -
hiwat
代表high water mark
最大入棧數(shù)量標記。(這個地方驗證上面的猜想纲菌,自動釋放池一定跟棧有關挠日。)
每一個自動釋放池都是由一系列AutoreleasePoolPage
組成的,并且每一個AutoreleasePoolPage
的大小都是4096字節(jié)
(16進制0x1000)
#define I386_PGBYTES 4096 /* bytes per 80386 page */
#define PAGE_SIZE I386_PGBYTES
-
雙向鏈表
自動釋放池中的AutoreleasePoolPage
是以雙向鏈表的形式連接起來的翰舌,這一點我們通過AutoreleasePoolPage
里面的參數(shù)parent
&child
就可以知道嚣潜。
-
AutoreleasePoolPageData
結構體的內(nèi)存大小為56
字節(jié):- 屬性
magic
的類型是magic_t
結構體,所占內(nèi)存大小為m[4]
---4*4 = 16字節(jié)
椅贱; - 屬性
next(指針)
懂算、thread(對象)
、parent(對象)
夜涕、child(對象)
均占8
字節(jié)犯犁,所以8*4 = 32字節(jié)
; - 屬性
depth
女器、hiwat
類型為uint32_t
酸役,實際類型為unsigned int
類型,均占4
字節(jié)驾胆,所以4*2 = 8字節(jié)
涣澡。
- 屬性
-
自動釋放池中的棧
-
POOL_BOUNDARY(哨兵對象)
在我們接著源碼分析之前,先來了解一個感念:POOL_BOUNDARY(哨兵對象)
哨兵對象
只是nil
的別名:
# define POOL_BOUNDARY nil
在每個自動釋放池初始化調(diào)用objc_autoreleasePoolPush
的時候丧诺,都會把一個POOL_BOUNDARY
Push到自動釋放池的棧頂入桂,并且返回這個POOL_BOUNDARY(哨兵對象)
。
而當方法objc_autoreleasePoolPop
被調(diào)用的時候驳阎,就會像自動釋放池中的對象發(fā)送release
消息抗愁,直到第一個POOL_BOUNDARY
。
objc_autoreleasePoolPush
通過上面的分析呵晚,我們已經(jīng)直到objc_autoreleasePoolPush
實際調(diào)用的就是push
方法蜘腌,那么我們就進入push
方法里面去一探究竟。
static inline void *push()
{
id *dest;
///判斷是否有pool
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
// 壓棧一個POOL_BOUNDARY饵隙,哨兵壓棧
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleaseFast
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);
}
}
上面分成了三種不同的情況:
-
1
:有hotPage
撮珠,并且當前page
不滿- 調(diào)用
page->add(obj)
方法,將對象添加至AutoreleasePoolPage
的棧中金矛。
- 調(diào)用
-
2
:有hotPage
并且page
已滿- 調(diào)用
autoreleaseFullOPage
初始化一個新的頁芯急; - 調(diào)用
page->add(obj)
方法勺届,將對象添加至AutoreleasePoolPage
的棧中(這個在autoreleaseFullOPage
函數(shù)里面有)。
- 調(diào)用
-
3
:無hotPage
- 調(diào)用
autoreleaseNoPage
創(chuàng)建一個hotPage
娶耍; - 調(diào)用
page->add(obj)
方法免姿,將對象添加到AutoreleasePoolPage
的棧中。
- 調(diào)用
通過上面可以看到伺绽,最后都會調(diào)用page->add(obj)
方法养泡,將對象添加到自動釋放池中嗜湃。hotPage
可以理解為當前正在使用的AutoreleasePoolPage
奈应。
page->add(obj)
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
這個方法其實就是一個壓棧的操作,將對象加入AutoreleasePoolPage
购披,飯后移動棧頂指針杖挣。
autoreleaseFullOPage
(當前的hotPage
已滿)
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);
}
它會從傳入的page
開始遍歷整個雙向鏈表,直到
1
:查找到一個未滿的AutoreleasePoolPage
刚陡。
2
:使用構造器傳入parent
創(chuàng)建一個新的AutoreleasePoolPage
在查找到一個可以使用的AutoreleasePoolPage
之后惩妇,會將該頁面標記成houPage
,然后調(diào)用page->add(obj)
方法筐乳,添加對象歌殃。
-
autoreleaseNoPage
(沒有hotPage
)
如果當前內(nèi)存中不存在hotPage
的時候,就會調(diào)用autoreleaseNoPage
方法初始化一個AutoreleasePoolPage
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);
}
由于是從新構建的自動釋放池的雙向鏈表蝙云,所以新的AutoreleasePoolPage
沒有parent
指針氓皱。
初始化之后,將當前頁標記為hotPage
勃刨,然后先向這個page
中添加一個POOL_BOUNDARY(哨兵對象)
波材,確保在pop
的時候不會報錯。
最后依然是page->add(obj)
身隐,將對象添加到自動釋放池廷区。
objc_autoreleasePoolPop
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以看到objc_autoreleasePoolPop
方法的調(diào)用,是有一個參數(shù)傳遞進來的贾铝。那么這個參數(shù)是什么呢隙轻?不知道大家還記不記得我們上面clang
出來的main.cpp
文件。我們再來看一下里面的代碼:
struct __AtAutoreleasePool {
///構造函數(shù)
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
///析構函數(shù)
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
大家發(fā)現(xiàn)沒有垢揩,傳入的參數(shù)就是push
壓棧后返回的哨兵對象atautoreleasepoolobj
玖绿。
通過上面我們已經(jīng)知道,objc_autoreleasePoolPop
最終調(diào)用的是pop
方法水孩,那么我們就來看一下pop
方法:
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// 判斷token是否是空占位符
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
// 獲取當前頁
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
// 如果當前頁不存在镰矿,則清楚空占位符
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
// 如果當前頁存在,則將當前頁設置為coldPage俘种,token設置為coldPage的開始位置
page = coldPage();
token = page->begin();
} else {
// 獲取token所在的頁
page = pageForPointer(token);
}
stop = (id *)token;
// 判斷最后一個位置秤标,是否是哨兵
if (*stop != POOL_BOUNDARY) {
// 進入 if 說明最后一個位置不是哨兵绝淡,也就是說最后一個位置是一個對象
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 {
// 出現(xiàn)混亂
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
// 出棧頁
return popPage<false>(token, page, stop);
}
- 我們在
pop
方法中看到牢酵,正常情況下,最后出棧調(diào)用的是popPage
方法衙猪;那么我們再來追蹤popPage
方法馍乙。
template<bool allowDebug>
static void
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
// 特殊情況:debug期間,刪除所有的池
// 獲取當前頁的父結點
AutoreleasePoolPage *parent = page->parent;
// 將當前頁kill
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();
}
}
}
可以看到popPage
中垫释,會通過releaseUntil
出棧當前頁stop
位置之前的所有對象丝格,即向棧中的對象發(fā)送release消息
,直到遇到傳入的哨兵對象
棵譬。還有就是kill
掉child頁
显蝌,這一步操作可能有什么其他的考慮,暫時不是很清楚订咸。但是既然是出棧曼尊,那重點就是releaseUntil
;延續(xù)我們之前的思路脏嚷,繼續(xù)追蹤releaseUntil
:
// 釋放 stop 位置之前的所有對象
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
// 判斷下一個對象是否是stop骆撇,如果不是繼續(xù)循環(huán)
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
// 獲取當前操作頁,即hot頁
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
// 如果當前頁為空
while (page->empty()) {
// 將page的父結點頁賦值個page
page = page->parent;
// 設置當前頁為父結點頁
setHotPage(page);
}
page->unprotect();
// page->next減減父叙,出棧
id obj = *--page->next;
// 將page->next位置的索引神郊,設置為SCRIBBLE,表示已經(jīng)被釋放
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
}
通過源碼我們開看到屿岂,raleaseUntil
主要是通過循環(huán)遍歷
,判斷當前對象是否是stop
鲸匿,其目的是釋放stop之前的所有對象
爷怀。
i
:首先通過page
的next
獲得對象,對next
進行減減
操作带欢,并且對索引進行更改运授;
ii
:判斷獲得的對象是否為哨兵對象
,如果不是乔煞,就釋放對象吁朦。
- 在上面我們還提到了
kill
,同過字面意思也能理解這個方法是做什么的渡贾。
它會將當前頁面以及子頁面全部銷毀逗宜。
不過我們還是再來看一期其內(nèi)部實現(xiàn):
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;
// 一直循環(huán)到最后一頁
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
探索了這么多,我們對于自動釋放池
的本質(zhì)、壓棧纺讲、出棧都有了一定的了解擂仍。但是還有一個知識點我們還沒探索到,那就是autorelease
熬甚。
autorelease
- 如果不是對象逢渔,或者是小對象,直接返回乡括;
- 如果是對象肃廓,則調(diào)用對象的
autorelease
方法,進行釋放诲泌。
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
跟進對象的autorelease
方法:
// objc_object::autorelease()inline id
objc_object::autorelease()
{
// 判斷是否是 `Tagged Pointer`,這個函數(shù)并不希望處理的對象是`Tagged Pointer`
ASSERT(!isTaggedPointer());
// 通過 `hasCustomRR`,檢查 類(包括其父類)中是否含有默認的方法
if (fastpath(!ISA()->hasCustomRR())) {
// 如果沒有盲赊,調(diào)用`rootAutorelease`函數(shù)
return rootAutorelease();
}
// 如果有,則調(diào)用自定義方法
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
??????
// objc_object::rootAutorelease()
inline id
objc_object::rootAutorelease()
{
// 如果是小對象档礁,直接返回
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
??????
// objc_object::rootAutorelease2()
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
??????
// AutoreleasePoolPage::autorelease((id)this)
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;
}
??????
// AutoreleasePoolPage::autoreleaseFast((id)this)
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);
}
}
所以autorelease
的函數(shù)調(diào)用棧是這個樣子的:
看到
autorelease
的函數(shù)調(diào)用棧之后角钩,不知道大家有沒有感到熟悉;沒錯呻澜,有很多方法,我們在上面探索objc_autoreleasePoolPush
的時候也見到過惨险。那么我們將autorelease
和我們的壓棧
結合起來羹幸,把整個流程串起來:自動釋放池出棧流程圖:
總結:
1、自動釋放池
是由AutoreleasePoolPage
以雙向鏈表的形式實現(xiàn)的辫愉。
2栅受、當對象調(diào)用autorelease
方法的時候,會將對象加入AutoreleasePoolPage
的棧中
3恭朗、調(diào)用AutoreleasePoolPage::pop
方法會向棧中的對象發(fā)送release
消息屏镊。也就是我們所說的出棧,主要通過page-next
的遞減
操作來完成痰腮,當出棧對象不是哨兵
的時候而芥,釋放對象。
-
Tips
-
assert
斷言
我們在源碼中發(fā)現(xiàn)有大量的斷言使用膀值,比如:ASSERT(!hotPage());
在源碼中棍丐,我們看到它是一個宏,我們跟進去看看這個宏是什么樣的:
-
// An assert that's disabled for release builds but still ensures the expression compiles.
#ifdef NDEBUG
#define ASSERT(x) (void)sizeof(!(x))
#else
#define ASSERT(x) assert(x)
#endif
*****************
我們看到官方注釋的很清楚沧踏,這個斷言不能用于發(fā)布模式歌逢。
assert
的作用是:校驗傳入的參數(shù)是否為真;如果為假翘狱,則向stderr
打印一條出錯信息秘案,然后通過abort
來終止應用程序。
assert
的缺點是:頻繁的調(diào)用會極大的影響程序的性能,增加額外的開銷阱高。
用法和注意事項:
1师骗、在函數(shù)開始處,檢驗傳入?yún)?shù)的合法性int resetBufferSize(int newSize) { assert(newSize >= 0); assert(newSize <= MaxSize); }
2讨惩、每個
assert
只校驗一個條件癞尚,因為同時校驗多個條件時,如果斷言失敗遗契,無法直觀的判斷是哪個條件失敗蛋欣。// 錯誤示范 assert(a > 0 && b > 0); // 正確示范 assert(a>0); assert(b>0);
3、不能使用改變環(huán)境的語句处面。
// 錯誤示范 如果在執(zhí)行之前 `i==100` , 那么這條語句就不會執(zhí)行厂置,那么`i++`這條命令就沒有執(zhí)行。 assert(i++ < 100); // 正確示范 assert(i < 100); i++;
4魂角、
assert
和后面的語句應空一行昵济,以形成邏輯個視覺上的一致感。
-
Tagged Pointer
Tagged Pointer
是一個特別的指針野揪,它分為兩個部分:
i
:一部分直接保存數(shù)據(jù)访忿;
ii
:一部分作為特殊標記,表示這個是一個特別的指針斯稳,不指向任何一個地方海铆。
因此Tagged Pointer
也被叫做偽指針-
Tagged Pointer
被設計的目的是用來存儲較小的對象,例如NSNumber
挣惰、NSDate
卧斟、NSString
等等。 -
Tagged Pointer
的值不再表示地址憎茂,而是真正的值珍语。 -
Tagged Pointer
在內(nèi)存讀取上有這3倍的效率,創(chuàng)建的時候比以前快106倍竖幔。
-
常見面試題
-
面試題1:臨時變量什么時候釋放板乙?
- 如果在正常情況下,一般是
超出作用域就會立即釋放
赏枚。 - 如果將臨時變量加入了
自動釋放池
亡驰,會延遲釋放,即在RunLoop休眠
或AutoreleasePool作用域之后
釋放饿幅。
- 如果在正常情況下,一般是
-
面試題2:AutoreleasePool原理
- 自動釋放池的本質(zhì)是一個
AutoreleasePoolPage結構體對象
凡辱,是一個棧結構存儲的頁
,每一個AutoreleasePoolPage
都是以雙向鏈表
的形式連接的栗恩。 - 自動釋放池的
壓棧
和出棧
主要是通過結構體的構造函數(shù)
和析構函數(shù)
調(diào)用底層的objc_autoreleasePoolPush
和objc_autoreleasePoolPop
透乾,進而調(diào)用AutoreleasePoolPage
的push
和pop
兩個方法。 - 每次調(diào)用
push
操作,其實就是創(chuàng)建一個新的AutoreleasePoolPage
乳乌,而AutoreleasePoolPage
的具體操作就是插入一個POOL_BOUNDARY(哨兵對象)
捧韵,并返回插入POOL_BOUNDARY
的內(nèi)存地址。而push
內(nèi)部調(diào)用autoreleaseFast
方法處理汉操,主要有以下三種情況:- 當
page存在再来,且不滿
的時候,調(diào)用add
方法將對象添加至page
的next
指針處磷瘤,并將next
遞增芒篷。 - 當
page存在,且已滿
的時候采缚,調(diào)用autoreleaseFullPage
初始化一個新的page
针炉,然后調(diào)用add
方法將對象添加至page棧
中。 - 當
page不存在
的時候扳抽,調(diào)用autoreleaseNoPage
創(chuàng)建一個hotPage
篡帕,然后調(diào)用add
方法,將對象添加到page棧
中贸呢。
- 當
- 自動釋放池的本質(zhì)是一個
當執(zhí)行
pop
操作的時候镰烧,會傳入一個值,這個值就是push
操作的返回值贮尉,即POOL_BOUNDARY
的內(nèi)存地址token
拌滋。所以pop
內(nèi)部的實現(xiàn)就是根據(jù)token
找到哨兵對象
所處的page(頁)
,然后使用objc_release
釋放token
之前的所有對象猜谚,并把next
指針指向正確的位置。-
面試題3:AutoreleasePool能否嵌套使用赌渣?
- 可以嵌套使用魏铅,其目的是
控制應用程序的內(nèi)存峰值
,使其不要太高坚芜。 - 可以嵌套使用的原因是因為:自動釋放池是以棧為結點览芳,通過雙向鏈表的形式連接的,且是和線程一一對應的鸿竖。
- 自動釋放池的
多層嵌套
其實就是不停的push哨兵對象
沧竟,在pop
時,會先釋放里面的缚忧,再釋放外面的悟泵。
- 可以嵌套使用魏铅,其目的是
-
面試題4:哪些對象可以加入AutoreleasePool?alloc創(chuàng)建的可以嗎闪水?
- 使用
new / alloc / copy
關鍵字生成的對象 和retain
了的對象需要手動釋放糕非,不會被添加到自動釋放池中。 - 設置為
autorelease
的對象不需要手動釋放
,會直接進入自動釋放池朽肥。 - 所有
autorelease
的對象禁筏,在出了作用域之后,會被自動添加到最新創(chuàng)建的自動釋放池之中衡招。
- 使用
-
面試題5:AutoreleasePool的釋放時機是什么時候篱昔?
- APP啟動之后,系統(tǒng)在主線程
RunLoop
里面注冊了兩個Observer
始腾,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHander()
州刽。 - 第一個
Observer
監(jiān)聽的事件是Entry(即將進入RunLoop)
,其回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPush()
創(chuàng)建自動釋放池窘茁。其order
是-2147183647
(優(yōu)先級最高)怀伦,保證創(chuàng)建自動釋放池發(fā)生在其他所有回調(diào)之前。 - 第二個
Observer
監(jiān)聽兩個事件:
1山林、BeforWaiting(準備進入休眠)
房待,這個時候調(diào)用_objc_autoreleasePoolPop()
&_objc_autoreleasePoolPush()
,釋放舊池并創(chuàng)建新池
2驼抹、Exit(即將推出Loop)
桑孩,這個時候調(diào)用_objc_autoreleasePoolPop()
釋放自動釋放池。這個Observer
的order
是2147483647
(優(yōu)先級最低)框冀,保證其釋放池子的操作發(fā)生在其他所有回調(diào)之后流椒。
- APP啟動之后,系統(tǒng)在主線程
-
面試題6:thread 和 AutoreleasePool的關系
- 每個線程(包括主線程在內(nèi)),都維護了
自己的自動釋放池堆棧結構
明也。 - 新的自動釋放池在被創(chuàng)建的時候宣虾,會被添加到
棧頂
;當自動釋放池銷毀的時候温数,會從棧
中移除绣硝。 - 對于
當前線程
來說,會將自動釋放池對象
放入自動釋放池的棧頂
撑刺;在線程停止的時候鹉胖,會自動釋放掉與該線程關聯(lián)的所有自動釋放池
。
- 每個線程(包括主線程在內(nèi)),都維護了
每個線程都有與之關聯(lián)的自動釋放池堆棧結構够傍,新的
pool
在創(chuàng)建的時候回被壓棧到棧頂甫菠;pool
銷毀的時候,會被出棧冕屯。
對于當前線程來說寂诱,釋放對象唄壓棧到棧頂,線程停止時愕撰,會自動釋放與之關聯(lián)的自動釋放池刹衫。
-
面試題7:RunLoop 和 AutoreleasePool的關系
- 主程序的
RunLoop
在每次事件循環(huán)
之前醋寝,會自動創(chuàng)建一個autoreleasepool
。 - 在
事件循環(huán)
結束的時候带迟,執(zhí)行drain
操作音羞,釋放其中的對象。
- 主程序的
參考資料
AutoReleasePool & NSRunLoop 底層分析
自動釋放池的前世今生 ---- 深入解析 autoreleasepool
斷言(assert)的用法
聊聊偽指針 Tagged Pointer