簡介
自動釋放池(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
實際是一個結構體绞呈,內(nèi)部首先執(zhí)行objc_autoreleasePoolPush()
,然后在調(diào)用objc_autoreleasePoolPop(atautoreleasepoolobj)
struct __AtAutoreleasePool {
**構造函數(shù)间景,在創(chuàng)建結構體時調(diào)用**
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
**析構函數(shù)佃声,在結構體銷毀的時候調(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的結構是否完整
magic_t const magic; //16字節(jié)
//下次新添加的autoreleased對象的位置,初始化時指向begin()
__unsafe_unretained id *next;//8字節(jié)
//當前線程
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ù)量標記
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
作用應該是起到標記的作用)-
當有
Autorelease
對象入棧時,會存放在next
指針指向的內(nèi)存空間隶症,然后next
指針指向Autorelease
對象地址后面的內(nèi)存空間
如下圖:
當前的
AutoreleasePoolPage
對象存放滿了政模,就會創(chuàng)建新的AutoreleasePoolPage
對象,用來存放Autorelease
對象蚂会。但自動釋放池結束時淋样,會調(diào)用
objc_autoreleasePoolPop()
函數(shù),然后調(diào)用AutoreleasePoolPage
的pop()
方法胁住,在pop()
方法內(nèi)部會通過next
指針找到最后一個入棧的Autorelease
對象趁猴,開始發(fā)送release
消息進行釋放刊咳,直到找到POOL_BOUNDARY
為止,這樣釋放池里面的Autorelease
對象就能全部釋放儡司。
Autorelease對象添加過程
當我們調(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);
}
}
通過源碼可以看到,當調(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
(準備進入休眠)和Exit(
即將推出Loop)庇配。BeforeWaiting
時調(diào)用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
釋放舊的池并創(chuàng)建新池斩跌;Exit
時_objc_autoreleasePoolPop()
來釋放自動釋放池;優(yōu)先級最低捞慌。
總結:
- 程序啟動時耀鸦,
Runloop
啟動,創(chuàng)建第一個自動釋放池啸澡,事件優(yōu)先級最高袖订。 Runloop
即將進入休眠時,清理需要釋放的對象嗅虏,調(diào)用pop()洛姑,事件優(yōu)先級最低。Runloop
退出時皮服,銷毀最后一個自動釋放池楞艾。Runloop
休眠時會釋放舊的并創(chuàng)建新的自動釋放池。
手動釋放
當特定場景時我們自己創(chuàng)建自動釋放池時冰更,在當前作用域大括號結束時釋放产徊。
手動使用AutoreleasePool場景
- 寫給予命令行的程序時,就是沒有UI框架蜀细;
- 寫循環(huán)舟铜,循環(huán)里邊包含了大量臨時創(chuàng)建的對象;
- 創(chuàng)建了新的線程奠衔;
- 長時間在后臺運行的任務
以上信息只用于本人學習使用谆刨,基本抄的此鏈接中的內(nèi)容塘娶,特此聲明