AutoReleasePool 自動(dòng)釋放池
AutoReleasePool
是OC的內(nèi)存自動(dòng)回收機(jī)制
,將加入到AutoReleasePool中的變量release時(shí)機(jī)延遲
。將對(duì)象加入到AutoReleasePool中,這個(gè)對(duì)象即使超出作用域也不會(huì)立即釋放,直到runloop休眠或者超出AutoReleasePool作用域
才會(huì)釋放
- 1、程序啟動(dòng)到加載完成,主線程對(duì)應(yīng)的Runloop處于
休眠
狀態(tài),直到用戶點(diǎn)擊交互喚醒Runloop - 2、用戶每次交互都會(huì)啟動(dòng)一次Runloop用來(lái)處理用戶的點(diǎn)擊、交互事件
- 3、Runloop被喚醒后,會(huì)
自動(dòng)創(chuàng)建AutoReleasePool
,并將所有延遲釋放的對(duì)象添加到AutoReleasePool
- 4、在一次完整的Runloop執(zhí)行結(jié)束前,會(huì)自動(dòng)向
AutoReleasePool
中的對(duì)象發(fā)送release消息
,然后銷毀AutoReleasePool
Clang 分析
- 在main.m中定義下面代碼,通過(guò)
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m
命令將其編譯成main.cpp
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
}
- 從.cpp文件中可以看出自動(dòng)釋放池的本質(zhì)是一個(gè)
結(jié)構(gòu)體對(duì)象
,調(diào)用objc_autoreleasePoolPush
和objc_autoreleasePoolPop
兩個(gè)方法
struct __AtAutoreleasePool {
//構(gòu)造函數(shù)
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析構(gòu)函數(shù)
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
{
//是一個(gè)結(jié)構(gòu)體
__AtAutoreleasePool __autoreleasepool;
}
return 0;
}
AutoreleasePoolPage 底層原理
在objc
源碼中,有關(guān)于AutoreleasePoolPage
的解釋
Autorelease pool implementation
- A thread's autorelease pool is a stack of pointers.
線程的自動(dòng)釋放池是指針的堆棧
- Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
每個(gè)指針都是要釋放的對(duì)象,或者是POOL_BOUNDARY,它是自動(dòng)釋放池的邊界。
- 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的指針。彈出池后,將釋放比哨點(diǎn)更熱的每個(gè)對(duì)象。
- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
堆棧分為兩個(gè)雙向鏈接的頁(yè)面列表。根據(jù)需要添加和刪除頁(yè)面。
- Thread-local storage points to the hot page, where newly autoreleased objects are stored.
線程本地存儲(chǔ)指向熱頁(yè)面,該頁(yè)面存儲(chǔ)新自動(dòng)釋放的對(duì)象。
-
objc_autoreleasePoolPush
和objc_autoreleasePoolPop
方法源碼
//***********push方法***********
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//***********pop方法***********
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
- 從上面可以發(fā)現(xiàn),都是調(diào)用
AutoreleasePoolPage
的push
和pop
方法,從AutoreleasePoolPage定義中可以發(fā)現(xiàn)自動(dòng)釋放池是一個(gè)頁(yè)
,也是一個(gè)對(duì)象
,大小是4096
//************宏定義************
#define PAGE_MIN_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
//************類定義************
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
friend struct thread_data_t;
public:
//頁(yè)的大小
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:
...
//構(gòu)造函數(shù)
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//開(kāi)始存儲(chǔ)的位置
objc_thread_self(),//傳的是當(dāng)前線程,當(dāng)前線程時(shí)通過(guò)tls獲取的
newParent,
newParent ? 1+newParent->depth : 0,//如果是第一頁(yè)深度為0,往后是前一個(gè)的深度+1
newParent ? newParent->hiwat : 0)
{...}
//析構(gòu)函數(shù)
~AutoreleasePoolPage() {...}
...
//頁(yè)的開(kāi)始位置
id * begin() {...}
//頁(yè)的結(jié)束位置
id * end() {...}
//頁(yè)是否為空
bool empty() {...}
//頁(yè)是否滿了
bool full() {...}
//頁(yè)的存儲(chǔ)是否少于一半
bool lessThanHalfFull() {...}
//添加釋放對(duì)象
id *add(id obj){...}
//釋放所有對(duì)象
void releaseAll() {...}
//釋放到stop位置之前的所有對(duì)象
void releaseUntil(id *stop) {...}
//殺掉
void kill() {...}
//釋放本地線程存儲(chǔ)空間
static void tls_dealloc(void *p) {...}
//獲取AutoreleasePoolPage
static AutoreleasePoolPage *pageForPointer(const void *p) {...}
static AutoreleasePoolPage *pageForPointer(uintptr_t p) {...}
//是否有空池占位符
static inline bool haveEmptyPoolPlaceholder() {...}
//設(shè)置空池占位符
static inline id* setEmptyPoolPlaceholder(){...}
//獲取當(dāng)前操作頁(yè)
static inline AutoreleasePoolPage *hotPage(){...}
//設(shè)置當(dāng)前操作頁(yè)
static inline void setHotPage(AutoreleasePoolPage *page) {...}
//獲取coldPage
static inline AutoreleasePoolPage *coldPage() {...}
//快速釋放
static inline id *autoreleaseFast(id obj){...}
//添加自動(dòng)釋放對(duì)象,當(dāng)頁(yè)滿的時(shí)候調(diào)用這個(gè)方法
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...}
//添加自動(dòng)釋放對(duì)象,當(dāng)沒(méi)頁(yè)的時(shí)候使用這個(gè)方法
static __attribute__((noinline))
id *autoreleaseNoPage(id obj){...}
//創(chuàng)建新頁(yè)
static __attribute__((noinline))
id *autoreleaseNewPage(id obj) {...}
public:
//自動(dòng)釋放
static inline id autorelease(id obj){...}
//入棧
static inline void *push() {...}
//兼容老的 SDK 出棧方法
__attribute__((noinline, cold))
static void badPop(void *token){...}
//出棧頁(yè)面
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop){...}
__attribute__((noinline, cold))
static void
popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){...}
//出棧
static inline void
pop(void *token){...}
static void init(){...}
//打印
__attribute__((noinline, cold))
void print(){...}
//打印所有
__attribute__((noinline, cold))
static void printAll(){...}
//打印Hiwat
__attribute__((noinline, cold))
static void printHiwat(){...}
- 從定義中發(fā)現(xiàn)
AutoreleasePoolPage
繼承自AutoreleasePoolPageData
- 從
AutoreleasePoolPageData
的定義中發(fā)現(xiàn)了parent
和child
,這代表AutoreleasePoolPageData
是一個(gè)雙向鏈表
,其內(nèi)存大小為56
字節(jié)
- 從
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
//用來(lái)校驗(yàn)AutoreleasePoolPage的結(jié)構(gòu)是否完整
magic_t const magic;//16個(gè)字節(jié)
//指向最新添加的autoreleased對(duì)象的下一個(gè)位置,初始化時(shí)指向begin()
__unsafe_unretained id *next;//8字節(jié)
//指向當(dāng)前線程
pthread_t const thread;//8字節(jié)
//指向父節(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的parent值為nil
AutoreleasePoolPage * const parent;//8字節(jié)
//指向子節(jié)點(diǎn),最后一個(gè)結(jié)點(diǎn)的child值為nil
AutoreleasePoolPage *child;//8字節(jié)
//表示深度,從0開(kāi)始,往后遞增1
uint32_t const depth;//4字節(jié)
//表示high water mark 最大入棧數(shù)量標(biāo)記
uint32_t hiwat;//4字節(jié)
//初始化
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)
{
}
};
objc_autoreleasePoolPush源碼
進(jìn)入push
源碼
- 判斷是否有pool
- 沒(méi)有,
autoreleaseNewPage
創(chuàng)建 - 有,
autoreleaseFast
方法壓棧一個(gè)哨兵對(duì)象
- 沒(méi)有,
//入棧
static inline void *push()
{
id *dest;
//判斷是否有pool
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.自動(dòng)釋放池從新池頁(yè)面開(kāi)始
//如果沒(méi)有,則創(chuàng)建
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//壓棧一個(gè)POOL_BOUNDARY,即壓棧哨兵
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleaseNewPage -- 創(chuàng)建頁(yè)
搜索objc_autoreleasePoolPush -> push -> autoreleaseNewPage
進(jìn)入源碼實(shí)現(xiàn),通過(guò)判斷獲取hotPage
,判斷當(dāng)前頁(yè)是否存在
- 存在,
autoreleaseFullPage
壓棧對(duì)象 - 不存在,
autoreleaseNoPage
創(chuàng)建新頁(yè)
//創(chuàng)建新頁(yè)
static __attribute__((noinline))
id *autoreleaseNewPage(id obj)
{
//獲取當(dāng)前操作頁(yè)
AutoreleasePoolPage *page = hotPage();
//如果存在,則壓棧對(duì)象
if (page) return autoreleaseFullPage(obj, page);
//如果不存在,則創(chuàng)建頁(yè)
else return autoreleaseNoPage(obj);
}
//******** hotPage方法 ********
//獲取當(dāng)前操作頁(yè)
static inline AutoreleasePoolPage *hotPage()
{
//獲取當(dāng)前頁(yè)
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
//如果是一個(gè)空池,則返回nil,否則,返回當(dāng)前線程的自動(dòng)釋放池
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}
//******** autoreleaseNoPage方法 ********
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;
//判斷是否是空占位符,如果是,則壓棧哨兵標(biāo)識(shí)符置為YES
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;
}
//如果對(duì)象不是哨兵對(duì)象,且沒(méi)有Pool,則報(bào)錯(cuò)
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;
}
//如果對(duì)象是哨兵對(duì)象,且沒(méi)有申請(qǐng)自動(dòng)釋放池內(nèi)存,則設(shè)置一個(gè)空占位符存儲(chǔ)在tls中,其目的是為了節(jié)省內(nèi)存
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {//如果傳入?yún)?shù)為哨兵
// 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();//設(shè)置空的占位符
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
//初始化第一頁(yè)
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//設(shè)置page為當(dāng)前聚焦頁(yè)
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
//壓棧哨兵的標(biāo)識(shí)符為YES,則壓棧哨兵對(duì)象
if (pushExtraBoundary) {
//壓棧哨兵
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
//壓棧對(duì)象
return page->add(obj);
}
- 通過(guò)
AutoreleasePoolPage
方法創(chuàng)建當(dāng)前線程的自動(dòng)釋放池,其中的構(gòu)造方法通過(guò)父類AutoreleasePoolPageData
初始化完成 -
begin()
:表示壓棧的位置(下一個(gè)要釋放對(duì)象的壓棧地址)
//********begin()********
//頁(yè)的開(kāi)始位置
id * begin() {
//等于 首地址+56(AutoreleasePoolPage類所占內(nèi)存大?。? return (id *) ((uint8_t *)this+sizeof(*this));
}
-
objc_thread_self()
:通過(guò)tls
獲取當(dāng)前線程
__attribute__((const))
static inline pthread_t objc_thread_self()
{
//通過(guò)tls獲取當(dāng)前線程
return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
-
newParent
:表示父節(jié)點(diǎn) - 最后兩個(gè)參數(shù)是
通過(guò)父節(jié)點(diǎn)的深度,最大入棧個(gè)數(shù)
計(jì)算depth和hiwat
//**********AutoreleasePoolPage構(gòu)造方法**********
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),//開(kāi)始存儲(chǔ)的位置
objc_thread_self(),//傳的是當(dāng)前線程,當(dāng)前線程時(shí)通過(guò)tls獲取的
newParent,
newParent ? 1+newParent->depth : 0,//如果是第一頁(yè)深度為0,往后是前一個(gè)的深度+1
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
//this 表示 新建頁(yè)面,將當(dāng)前頁(yè)面的子節(jié)點(diǎn) 賦值為新建頁(yè)面
parent->child = this;
parent->protect();
}
protect();
}
//**********AutoreleasePoolPageData初始化方法**********
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)
{
}
自動(dòng)釋放池內(nèi)存結(jié)構(gòu)
-
切換到MRC環(huán)境下,
Build Settings -> Objectice-C Automatic Reference Counting
設(shè)置為NO
image.png 定義下面代碼,并運(yùn)行
//************打印自動(dòng)釋放池結(jié)構(gòu)************
extern void _objc_autoreleasePoolPrint(void);
//************運(yùn)行代碼************
int main(int argc, const char * argv[]) {
@autoreleasepool {
//循環(huán)創(chuàng)建對(duì)象,并加入自動(dòng)釋放池
for (int i = 0; i < 5; i++) {
NSObject *objc = [[NSObject alloc] sutorelease];
}
//調(diào)用
_objc_autoreleasePoolPrint();
}
}
發(fā)現(xiàn)打印了6個(gè)對(duì)象,其中POOL表示哨兵
,即邊界,防止越界,頁(yè)的首地址和POOL相差0x38(即56),也就是AutoreleasePoolPage
自身大小
將測(cè)試代碼數(shù)據(jù)5改為505,發(fā)現(xiàn)第一頁(yè)只存儲(chǔ)了504個(gè)對(duì)象,第二頁(yè)存儲(chǔ)了1個(gè)
將測(cè)試代碼數(shù)據(jù)5改為505+506,發(fā)現(xiàn)第一頁(yè)只存儲(chǔ)了504個(gè)對(duì)象,第二頁(yè)存儲(chǔ)了505個(gè),第三頁(yè)存儲(chǔ)了2個(gè)
autoreleaseFast 壓棧對(duì)象
進(jìn)入autoreleaseFast
源碼
- 獲取當(dāng)前操作頁(yè)
- 存在且未滿,add方法壓棧
- 存在且滿了,autoreleaseFullPage進(jìn)入下一頁(yè)
- 不存在,autoreleaseNoPage創(chuàng)建新頁(yè)
static inline id *autoreleaseFast(id obj)
{
//獲取當(dāng)前操作頁(yè)
AutoreleasePoolPage *page = hotPage();
//判斷頁(yè)是否滿了
if (page && !page->full()) {
//如果未滿,則壓棧
return page->add(obj);
} else if (page) {
//如果滿了,則安排新的頁(yè)面
return autoreleaseFullPage(obj, page);
} else {
//頁(yè)不存在,則新建頁(yè)
return autoreleaseNoPage(obj);
}
}
autoreleaseFullPage
判斷當(dāng)前頁(yè)是否存儲(chǔ)滿了,如果滿了通過(guò)do-while循環(huán)
查找子節(jié)點(diǎn)對(duì)應(yīng)的頁(yè)
,如果沒(méi)有子節(jié)點(diǎn)就創(chuàng)建新的頁(yè)并壓棧
//添加自動(dòng)釋放對(duì)象,當(dāng)頁(yè)滿的時(shí)候調(diào)用這個(gè)方法
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-while遍歷循環(huán)查找界面是否滿了
do {
//如果子頁(yè)面存在,則將頁(yè)面替換為子頁(yè)面
if (page->child) page = page->child;
//如果子頁(yè)面不存在,則新建頁(yè)面
else page = new AutoreleasePoolPage(page);
} while (page->full());
//設(shè)置為當(dāng)前操作頁(yè)面
setHotPage(page);
//對(duì)象壓棧
return page->add(obj);
}
add方法
添加釋放對(duì)象
,通過(guò)next指針存儲(chǔ)釋放的對(duì)象,并next指針遞增,表示下一個(gè)釋放對(duì)象的存儲(chǔ)位置
//添加釋放對(duì)象
id *add(id obj)
{
ASSERT(!full());
unprotect();
//傳入對(duì)象存儲(chǔ)的位置
id *ret = next; // faster than `return next-1` because of aliasing
//將obj壓棧到next指針位置,然后next進(jìn)行++,即下一個(gè)對(duì)象存儲(chǔ)的位置
*next++ = obj;
protect();
return ret;
}
autorelease底層
查看autorelease
源碼
- 如果不是對(duì)象,直接返回
- 如果是小對(duì)象,直接返回
- 調(diào)用
autorelease
釋放
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
//如果不是對(duì)象,則直接返回
if (!obj) return obj;
//如果是小對(duì)象,也直接返回
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
進(jìn)入autorelease
源碼,無(wú)論是壓棧哨兵對(duì)象還是普通對(duì)象都會(huì)進(jìn)入該方法,只是區(qū)別標(biāo)志不同
??
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
//判斷是否是自定義類
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
??
inline id
objc_object::rootAutorelease()
{
//如果是小對(duì)象,直接返回
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
??
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
??
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
//autoreleaseFast 壓棧操作
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
objc_autoreleasePoolPop 源碼分析
objc_autoreleasePoolPop
方法中傳入一個(gè)對(duì)象---push壓棧后的哨兵對(duì)象
,即ctxt
,防止出?;靵y
- 進(jìn)入
pop
源碼- 空頁(yè)處理
- 根據(jù)token獲取page
- 容錯(cuò)處理
-
popPage
出棧
//出棧
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
//判斷對(duì)象是否是空占位符
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
//如果當(dāng)是空占位符
// Popping the top-level placeholder pool.
//獲取當(dāng)前頁(yè)
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
//如果當(dāng)前頁(yè)不存在,則清除空占位符
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
//如果當(dāng)前頁(yè)存在,則將當(dāng)前頁(yè)設(shè)置為coldPage,token設(shè)置為coldPage的開(kāi)始位置
page = coldPage();
token = page->begin();
} else {
//獲取token所在的頁(yè)
page = pageForPointer(token);
}
stop = (id *)token;
//判斷最后一個(gè)位置,是否是哨兵
if (*stop != POOL_BOUNDARY) {
//最后一個(gè)位置不是哨兵,即最后一個(gè)位置是一個(gè)對(duì)象
if (stop == page->begin() && !page->parent) {
//如果是第一個(gè)位置,且沒(méi)有父節(jié)點(diǎn),什么也不做
// 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 {
//如果是第一個(gè)位置,且有父節(jié)點(diǎn),則出現(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);
}
//出棧頁(yè)
return popPage<false>(token, page, stop);
}
- 進(jìn)入
popPage
源碼,其中傳入的allowDebug
為false,通過(guò)releaseUntil
出棧,即向棧中對(duì)象發(fā)送release消息
,知道遇到哨兵對(duì)象
//出棧頁(yè)面
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//出棧當(dāng)前操作頁(yè)面對(duì)象
page->releaseUntil(stop);
// memory: delete empty children 刪除空子項(xiàng)
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
//調(diào)試期間刪除每個(gè)特殊情況下的所有池
//獲取當(dāng)前頁(yè)面的父節(jié)點(diǎn)
AutoreleasePoolPage *parent = page->parent;
//將當(dāng)前頁(yè)面殺掉
page->kill();
//設(shè)置操作頁(yè)面為父節(jié)點(diǎn)頁(yè)面
setHotPage(parent);
}
else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
//特殊情況:調(diào)試丟失的自動(dòng)釋放池時(shí)刪除pop(top)的所有內(nèi)容
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full 如果頁(yè)面已滿一半以上,則保留一個(gè)空子級(jí)
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
- 進(jìn)入
releaseUntil
源碼,通過(guò)while循環(huán)
,判斷對(duì)象是否stop,釋放哨兵對(duì)象之前的所有對(duì)象
//釋放到stop位置之前的所有對(duì)象
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack 不是遞歸的:我們不想破壞堆棧
// if a thread accumulates a stupendous amount of garbage
//判斷下一個(gè)對(duì)象是否等于stop,如果不等于,則進(jìn)入while循環(huán)
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects 每次從hotPage()重新啟動(dòng),以防-release自動(dòng)釋放更多對(duì)象
//獲取當(dāng)前操作頁(yè)面,即hot頁(yè)面
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
//如果當(dāng)前頁(yè)是空的
while (page->empty()) {
//將page賦值為父節(jié)點(diǎn)頁(yè)
page = page->parent;
//并設(shè)置當(dāng)前頁(yè)為父節(jié)點(diǎn)頁(yè)
setHotPage(page);
}
page->unprotect();
//next進(jìn)行--操作,即出棧
id obj = *--page->next;
//將頁(yè)索引位置置為SCRIBBLE,表示已經(jīng)被釋放
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
//釋放
objc_release(obj);
}
}
//設(shè)置當(dāng)前頁(yè)
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
- 進(jìn)入
kell
實(shí)現(xiàn),銷毀當(dāng)前頁(yè),將當(dāng)前頁(yè)賦值為父節(jié)點(diǎn)頁(yè)
,并將父節(jié)點(diǎn)頁(yè)的child對(duì)象指針置為nil
//銷毀
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;
//獲取最后一個(gè)頁(yè)
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
//子節(jié)點(diǎn) 變成 父節(jié)點(diǎn)
page = page->parent;
if (page) {
page->unprotect();
//子節(jié)點(diǎn)為nil
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
總結(jié)
自動(dòng)釋放池 -- 內(nèi)存結(jié)構(gòu)
- 只有
第一頁(yè)
有哨兵對(duì)象 - 當(dāng)一頁(yè)壓棧滿了會(huì)開(kāi)辟新頁(yè),第一頁(yè)最多存儲(chǔ)
504
個(gè)對(duì)象,其余最多存儲(chǔ)505
個(gè) -
一頁(yè)大小等于505*8=4040
AutoreleasePool內(nèi)存結(jié)構(gòu)圖
自動(dòng)釋放池 -- push壓棧
- 當(dāng)沒(méi)有pool,創(chuàng)建新頁(yè),壓棧
哨兵對(duì)象
- 在頁(yè)中通過(guò)
next指針遞增
壓棧對(duì)象 - 當(dāng)頁(yè)滿了,
創(chuàng)建新頁(yè)
并設(shè)置當(dāng)前頁(yè)的child對(duì)象為新頁(yè)
自動(dòng)釋放池 -- pop出棧
- 通過(guò)
next指針遞減
出棧普通對(duì)象
- 當(dāng)前頁(yè)空了,設(shè)置父節(jié)點(diǎn)頁(yè)為hot頁(yè),kill當(dāng)前頁(yè)
面試題
1、臨時(shí)變量什么時(shí)候釋放
- 正常情況下,
超出作用域立即釋放
- 加入自動(dòng)釋放池,會(huì)延遲釋放,在
Runloop休眠或者autoreleasepool 作用域之后釋放
2、AutoreleasePool原理
- 自動(dòng)釋放池本質(zhì)是一個(gè)
AutoreleasePoolPage結(jié)構(gòu)體對(duì)象
,棧結(jié)構(gòu)存儲(chǔ)
,每一個(gè)AutoreleasePoolPage以雙向鏈表
形式連接 - 自動(dòng)釋放的壓棧和出棧本質(zhì)上是調(diào)用
AutoreleasePoolPage
的push
和pop
方法 -
push 壓棧
- 判斷hotPage是否存在
- 不存在,
autoreleaseNoPage
創(chuàng)建新hotPage,調(diào)用add方法將對(duì)象添加至page棧中 - 存在滿了,
autoreleaseFullPage
初始新的page - 存在沒(méi)滿,調(diào)用add方法將對(duì)象添加到page的next指針,next指針++
- 不存在,
- 判斷hotPage是否存在
-
pop 出棧
執(zhí)行pop出棧時(shí),會(huì)傳入push操作的返回值,即POOL_BOUNDARY
的內(nèi)存地址token
,根據(jù)token找到哨兵對(duì)象所在,并釋放之前的對(duì)象,next指針--
3、AutoreleasePool能否嵌套使用?
- 可以嵌套使用,
控制App內(nèi)存峰值
- 自動(dòng)釋放池是以棧為節(jié)點(diǎn),雙向鏈表的形式,與線程一一對(duì)應(yīng)
4、哪些對(duì)象可以加入AutoreleasePool?alloc創(chuàng)建可以嗎?
- 使用
new、alloc、copy
關(guān)鍵字生成的對(duì)象和retain
了的對(duì)象需要手動(dòng)釋放
,不會(huì)被加入自動(dòng)釋放池 - 設(shè)置了
autorelease
的對(duì)象會(huì)直接進(jìn)入自動(dòng)釋放池 - 所有
autorelease
對(duì)象出了作用域后,會(huì)被加入最近的自動(dòng)釋放池
5、main函數(shù)中自動(dòng)釋放池什么時(shí)候銷毀
- App在啟動(dòng)時(shí),會(huì)在
主Runloop
中注冊(cè)兩個(gè)Observer
,回調(diào)_wrapRunLoopWithAutoreleasePoolHandler ()
- 第一個(gè)Observer監(jiān)聽(tīng)
Entry(即將進(jìn)入Loop)
,在其回調(diào)中調(diào)用_objc_autoreleasePoolPush ()
創(chuàng)建自動(dòng)釋放池,優(yōu)先級(jí)最高,保證創(chuàng)建的自動(dòng)釋放池在所有回調(diào)前 - 第二個(gè)Observer監(jiān)聽(tīng)兩個(gè)事件
-
BeforeWaiting
(準(zhǔn)備進(jìn)入休眠)時(shí)調(diào)用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush ()
方法,釋放舊池,創(chuàng)建新池 -
Exit
(退出Loop)時(shí)調(diào)用_objc_autoreleasePoolPop ()
釋放,優(yōu)先級(jí)最低,保證所有回調(diào)已經(jīng)完成
-
6、 線程 和 自動(dòng)釋放池 的關(guān)系
官方文檔
Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.
- 每個(gè)線程(包括主線程)都在內(nèi)部維護(hù)了自己的
自動(dòng)釋放出堆棧結(jié)構(gòu)
- 新的自動(dòng)釋放池創(chuàng)建時(shí),會(huì)被添加到
棧頂
,當(dāng)自動(dòng)釋放池銷毀時(shí),從棧中移除 - 對(duì)應(yīng)當(dāng)前線程來(lái)說(shuō),會(huì)將自動(dòng)釋放的對(duì)象加入自動(dòng)釋放池,當(dāng)線程停止時(shí),會(huì)自動(dòng)釋放與該線程相關(guān)的所有自動(dòng)釋放池
7、RunLoop 和 AutoreleasePool的關(guān)系
官方文檔
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.
- Runloop在沒(méi)錯(cuò)事件循環(huán)之前,都會(huì)自動(dòng)創(chuàng)建一會(huì)
AutoreleasePool
- 在事件結(jié)束時(shí),執(zhí)行
drain
,釋放其中的對(duì)象