簡介
自動釋放池(autoreleasepool)是OC的一種內(nèi)存自動回收機制。正常情況下,創(chuàng)建的變量超出作用域時釋放,自動釋放池可以延遲對象的釋放。
原理
OC代碼
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
使用clang命令將OC代碼重寫成C++
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
C++代碼
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
__AtAutoreleasePool實際是一個結(jié)構(gòu)體,內(nèi)部首先執(zhí)行objc_autoreleasePoolPush(),然后在調(diào)用objc_autoreleasePoolPop(atautoreleasepoolobj)
struct __AtAutoreleasePool {
**構(gòu)造函數(shù),在創(chuàng)建結(jié)構(gòu)體時調(diào)用**
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
**析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時候調(diào)用**
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
objc4-818源碼
源碼中可以看出,objc_autoreleasePoolPush()函數(shù)內(nèi)調(diào)用了AutoreleasePoolPage的push()方法,objc_autoreleasePoolPop()則調(diào)用了AutoreleasePoolPage的pop()方法。
也就是說,需要Autorelease的對象,都是由AutoreleasePoolPage對象來管理的。
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage對象源碼:
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
////用來校驗AutoreleasePoolPage的結(jié)構(gòu)是否完整
magic_t const magic; //16字節(jié)
//下次新添加的autoreleased對象的位置,初始化時指向begin()
__unsafe_unretained id *next;//8字節(jié)
//當(dāng)前線程
pthread_t const thread;//8字節(jié)
//指向父節(jié)點,即上一個頁面,第一個頁面的parent值為nil
AutoreleasePoolPage * const parent;//8字節(jié)
//指向子節(jié)點,即下一個頁面,最后一個頁面的child值為nil
AutoreleasePoolPage *child;//8字節(jié)
//表示頁面深度,從0開始,往后遞增1
uint32_t const depth; //4字節(jié)
//high water mark,表示最大入棧數(shù)量標(biāo)記
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)
{
}
};
一個AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,除了存放自己內(nèi)部變量以外,剩下的內(nèi)存空間就用來存放需要Autorelease的對象地址。
所有AutoreleasePoolPage對象都是以棧為節(jié)點通過雙向鏈表的形式連接在一起
AutoreleasePool是由多個AutoreleasePoolPage對象組成的,以雙向鏈表組成,其中parent指針指向上一個AutoreleasePoolPage對象,child指針指向下一個AutoreleasePoolPage對象,如下圖:

AutoreleasePoolPage管理Autorelease對象的過程
AutoreleasePoolPage對象中的next指針指向下一個能存放Autorelease對象地址的區(qū)域。上文講到,
push()調(diào)用時,內(nèi)部會將一個POOL_BOUNDARY哨兵入棧,并返回其存放的內(nèi)存地址,然后next指針指向POOL_BOUNDARY后面第一個內(nèi)存地址。(POOL_BOUNDARY作用應(yīng)該是起到標(biāo)記的作用)-
當(dāng)有
Autorelease對象入棧時,會存放在next指針指向的內(nèi)存空間,然后next指針指向Autorelease對象地址后面的內(nèi)存空間
如下圖:
當(dāng)前的
AutoreleasePoolPage對象存放滿了,就會創(chuàng)建新的AutoreleasePoolPage對象,用來存放Autorelease對象。但自動釋放池結(jié)束時,會調(diào)用
objc_autoreleasePoolPop()函數(shù),然后調(diào)用AutoreleasePoolPage的pop()方法,在pop()方法內(nèi)部會通過next指針找到最后一個入棧的Autorelease對象,開始發(fā)送release消息進行釋放,直到找到POOL_BOUNDARY為止,這樣釋放池里面的Autorelease對象就能全部釋放。
Autorelease對象添加過程
當(dāng)我們調(diào)用autorelease方法時,底層做了哪些操作
**autorelease 函數(shù)內(nèi)部實現(xiàn)**
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
rootAutorelease內(nèi)部實現(xiàn)
id _objc_rootAutorelease(id obj)
{
assert(obj);
return obj->rootAutorelease();
}
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
rootAutorelease2函數(shù)內(nèi)部實現(xiàn)
id objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
AutoreleasePoolPage 對象的autorelease 函數(shù)實現(xiàn)
public:
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;
}
autoreleaseFast 函數(shù)內(nèi)部實現(xiàn)
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);
}
}
通過源碼可以看到,當(dāng)調(diào)用autorelease方法時:
- 底層調(diào)用
rootAutorelease函數(shù),然后調(diào)用rootAutorelease2函數(shù),然后調(diào)用AutoreleasePoolPage對象的autorelease方法 -
AutoreleasePoolPage對象的autorelease方法調(diào)用autoreleaseFast函數(shù),函數(shù)內(nèi)將Autorelease對象添加到AutoreleasePoolPage中。
自動釋放池釋放時機
系統(tǒng)自動釋放
自動釋放池寄生于Runloop:程序啟動后,主線程會注冊兩個Observer,回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
1、監(jiān)測Enter(即將進入Loop)狀態(tài),回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPush()創(chuàng)建自動釋放池,優(yōu)先級最高。
2、監(jiān)測BeforeWaiting(準(zhǔn)備進入休眠)和Exit(即將推出Loop)。BeforeWaiting時調(diào)用_objc_autoreleasePoolPop()和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit時_objc_autoreleasePoolPop() 來釋放自動釋放池;優(yōu)先級最低。
總結(jié):
- 程序啟動時,
Runloop啟動,創(chuàng)建第一個自動釋放池,事件優(yōu)先級最高。 Runloop即將進入休眠時,清理需要釋放的對象,調(diào)用pop(),事件優(yōu)先級最低。Runloop退出時,銷毀最后一個自動釋放池。Runloop休眠時會釋放舊的并創(chuàng)建新的自動釋放池。
手動釋放
當(dāng)特定場景時我們自己創(chuàng)建自動釋放池時,在當(dāng)前作用域大括號結(jié)束時釋放。
手動使用AutoreleasePool場景
- 寫給予命令行的程序時,就是沒有UI框架;
- 寫循環(huán),循環(huán)里邊包含了大量臨時創(chuàng)建的對象;
- 創(chuàng)建了新的線程;
- 長時間在后臺運行的任務(wù)
以上信息只用于本人學(xué)習(xí)使用,基本抄的此鏈接中的內(nèi)容,特此聲明
