自動(dòng)釋放池
自動(dòng)釋放池
是 OC 的一種 內(nèi)存自動(dòng)回收機(jī)制
栋豫。它可以延遲加入 AutoreleasePool
中的變量 release
的時(shí)機(jī),即當(dāng)我們創(chuàng)建了一個(gè)對(duì)象虚茶,并把他加入到了自動(dòng)釋放池中時(shí)戈鲁,他不會(huì)立即被釋放,會(huì)等到一次 runloop
結(jié)束或者作用域超出 {}
或者超出 [pool release]
之后再被釋放嘹叫。下面我們通過(guò)三種方式分別來(lái)解析
Clang 分析
創(chuàng)建一個(gè)空工程婆殿,切換到 main.m
文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
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);
}
通過(guò)終端命令 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.2.sdk main.m
導(dǎo)出 cpp
文件,打開(kāi) main.cpp
文件罩扇,查看 main
函數(shù)的實(shí)現(xiàn)源碼
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
// __AtAutoreleasePool 結(jié)構(gòu)體
struct __AtAutoreleasePool {
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
從上面源碼中看到婆芦,__AtAutoreleasePool
是一個(gè)結(jié)構(gòu)體,有 構(gòu)造函數(shù) + 析構(gòu)函數(shù)
喂饥,結(jié)構(gòu)體定義的對(duì)象在作用域結(jié)束后消约,會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)。本質(zhì)也是一個(gè)對(duì)象
@autoreleasepool {}
//等價(jià)于
{__AtAutoreleasePool __autoreleasepool; }
匯編分析
在 main
函數(shù)中打個(gè)斷點(diǎn)员帮,運(yùn)行程序或粮,開(kāi)啟匯編模式
通過(guò)調(diào)試的結(jié)果發(fā)現(xiàn),跟 clang
分析的結(jié)果一致
底層分析
關(guān)于 AutoreleasePool
捞高,在 objc 源碼中有一段解釋
/***********************************************************************
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.
**********************************************************************/
- 線程的自動(dòng)釋放池是指針的堆棧氯材。
- 每個(gè)指針都是要釋放的對(duì)象(或者是 POOL_BOUNDARY,POOL_BOUNDARY 是自動(dòng)釋放池的邊界)硝岗。
- 一個(gè)自動(dòng)釋放池的標(biāo)示是指向該池的 POOL_BOUNDARY 的指針氢哮。 當(dāng)自動(dòng)釋放池出棧,將釋放比哨兵更熱的每個(gè)對(duì)象型檀。
- 堆棧被分為一個(gè)雙向鏈接的頁(yè)面列表(page)冗尤。 根據(jù)需要添加和刪除頁(yè)面。
- 線程本地存儲(chǔ)指向熱頁(yè)面胀溺,該頁(yè)面存儲(chǔ)新自動(dòng)釋放的對(duì)象裂七。
自動(dòng)釋放池是什么時(shí)候創(chuàng)建的?對(duì)象是如何加入自動(dòng)釋放池的月幌?哪些對(duì)象才會(huì)加入自動(dòng)釋放池碍讯?帶著這些疑問(wèn)悬蔽,我們來(lái)一步步探索自動(dòng)釋放池的底層原理
AutoreleasePoolPage
我們根據(jù)前面 clang
以及匯編的分析扯躺,自動(dòng)釋放池的底層是調(diào)用了 objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
兩個(gè)方法,我們進(jìn)入 objc781
查看其源碼實(shí)現(xiàn),如下:
// push
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
// pop
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
從上面的源碼中我們看到录语,兩個(gè)方法走的分別是 AutoreleasePoolPage
的 push
和 pop
實(shí)現(xiàn)倍啥,那么進(jìn)入 AutoreleasePoolPage
的源碼看下是如何定義的
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:
...
// 構(gòu)造函數(shù)
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0) {...}
// 析構(gòu)函數(shù)
~AutoreleasePoolPage(){...}
...
// 添加釋放對(duì)象
id *add(id obj) {...}
//釋放所有對(duì)象
void releaseAll() {...}
...
// 獲取AutoreleasePoolPage
static AutoreleasePoolPage *pageForPointer(const void *p) {...}
static AutoreleasePoolPage *pageForPointer(uintptr_t p) {...}
...
// 獲取當(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)建新的page
static __attribute__((noinline))
id *autoreleaseNewPage(id obj) {...}
public:
// 自動(dòng)釋放
static inline id autorelease(id obj) {...}
//入棧
static inline void *push() {...}
...
// 出棧
static inline void
pop(void *token) {...}
...
#undef POOL_BOUNDARY
};
從 AutoreleasePoolPage
定義發(fā)現(xiàn)虽缕,它是一個(gè) page
,同時(shí)也是一個(gè) 對(duì)象
蒲稳,這個(gè)頁(yè)的大小為 4096
字節(jié)氮趋。AutoreleasePoolPage
繼承自 AutoreleasePoolPageData
,那么它的結(jié)構(gòu)是什么樣呢江耀?如下
class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
// 用來(lái)校驗(yàn) AutoreleasePoolPage 的結(jié)構(gòu)是否完整
magic_t const magic;
// 指向最新添加的 autoreleased 對(duì)象的下一個(gè)位置剩胁,初始化時(shí)指向 begin()
__unsafe_unretained id *next;
// 指向當(dāng)前線程
pthread_t const thread;
// 指向父節(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的 parent 值為 nil
AutoreleasePoolPage * const parent;
// 指向子節(jié)點(diǎn)祥国,最后一個(gè)結(jié)點(diǎn)的 child 值為 nil
AutoreleasePoolPage *child;
// 表示深度昵观,從 0 開(kāi)始,往后遞增 1
uint32_t const depth;
// 表示 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)
{
}
};
從 AutoreleasePoolPageData
結(jié)構(gòu)看到了 AutoreleasePoolPage
舌稀,這里也間接的證明了自動(dòng)釋放池是一個(gè) 雙向鏈表的頁(yè)
結(jié)構(gòu)啊犬。
AutoreleasePoolPageData
的內(nèi)存大小為 56 字節(jié),magic_t
結(jié)構(gòu)體占用內(nèi)存為m[4]
壁查,占內(nèi)存為 16 字節(jié)(4*4)觉至;屬性next(指針)、thread(對(duì)象)睡腿、parent(對(duì)象)康谆、child(對(duì)象)
均占 8 字節(jié),共 32 字節(jié)嫉到;uint32_t
兩個(gè)各占 4 字節(jié)沃暗,共 8字節(jié)。
objc_autoreleasePoolPush
通過(guò)上面的分析何恶,我們進(jìn)入 AutoreleasePoolPage
的 push
實(shí)現(xiàn)孽锥,如下
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) { //1.
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY); //2.
} else {
dest = autoreleaseFast(POOL_BOUNDARY);//3.
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
- 判斷是否創(chuàng)建過(guò) pool
- 沒(méi)有,創(chuàng)建一個(gè)新的 page
- 壓棧一個(gè) POOL_BOUNDARY(哨兵)
1. autoreleaseNewPage(創(chuàng)建新的頁(yè))
首先需要判斷 hotPage
(當(dāng)前頁(yè))是否存在细层,執(zhí)行后續(xù)操作
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);
}
// 獲取當(dāng)前操作頁(yè)(hotPage)
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
// 如果是個(gè)空池惜辑,返回 nil
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
//返回當(dāng)前線程的自動(dòng)釋放池
if (result) result->fastcheck();
return result;
}
- 如果存在
hotpage
,則通過(guò)autoreleaseFullPage
方法壓棧對(duì)象
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);
// 當(dāng)前頁(yè)面滿了疫赎,遍歷循環(huán)子頁(yè)面
do {
// 如果子頁(yè)面存在盛撑,將當(dāng)前頁(yè)面替換為子頁(yè)面
if (page->child) page = page->child;
// 子頁(yè)面不存在,則新建
else page = new AutoreleasePoolPage(page);
} while (page->full());
// 設(shè)置當(dāng)前操作頁(yè)面
setHotPage(page);
// 對(duì)象壓棧
return page->add(obj);
}
通過(guò)操作
child
對(duì)象捧搞,將當(dāng)前頁(yè)的child
指向新建頁(yè)面抵卫,建立雙向鏈表連接
- 如果不存在
hotpage
狮荔,通過(guò)autoreleaseNoPage
創(chuàng)建頁(yè)
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è)空占位符
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.
// 初始化首頁(yè)
// Install the first page.
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) {
// 壓棧哨兵對(duì)象
page->add(POOL_BOUNDARY);
}
//壓棧對(duì)象
// Push the requested object or pool.
return page->add(obj);
}
當(dāng)前頁(yè)面不存在或者子頁(yè)面不存在時(shí)慨亲,通過(guò) AutoreleasePoolPage
的構(gòu)造方法創(chuàng)建新的 AutoreleasePoolPage
婚瓜,而它的構(gòu)造方法實(shí)現(xiàn)是通過(guò) AutoreleasePoolPageData
的初始化方法來(lái)的
/**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,
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)
{
}
-
begin()
表示壓棧的位置(即下一個(gè)釋放對(duì)象的壓棧地址)闰渔。當(dāng)前頁(yè)面首地址+56(56 是AutoreleasePoolPageData
的內(nèi)存大小)
id * begin() {
// sizeof(*this) = 56
return (id *) ((uint8_t *)this+sizeof(*this));
}
-
objc_thread_self()
表示當(dāng)前線程铐望,而當(dāng)前線程時(shí)通過(guò) tls 獲取的
__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
表示的是AutoreleasePoolPageData
的父節(jié)點(diǎn)newParent ? 1+newParent->depth : 0
表示通過(guò)父節(jié)點(diǎn)的深度計(jì)算depth
newParent ? newParent->hiwat : 0
表示通過(guò)父節(jié)點(diǎn)的最大入棧個(gè)數(shù)計(jì)算hiwat
add 方法
添加釋放對(duì)象冈涧,其底層是實(shí)現(xiàn)是通過(guò) next
指針存儲(chǔ)釋放對(duì)象,并將 next
指針遞增正蛙,表示下一個(gè)釋放對(duì)象存儲(chǔ)的位置督弓。從這里可以看出頁(yè)是通過(guò)棧結(jié)構(gòu)存儲(chǔ)
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;
}
2. autoreleaseFast(壓棧對(duì)象)
源碼實(shí)現(xiàn)如下
static inline id *autoreleaseFast(id obj)
{
// 獲取當(dāng)前操作頁(yè)
AutoreleasePoolPage *page = hotPage();
// 當(dāng)前操作頁(yè)面存在愚隧,且頁(yè)面未存滿
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
// 當(dāng)前操作頁(yè)面已經(jīng)存滿了
return autoreleaseFullPage(obj, page);
} else {
// 當(dāng)前操作頁(yè)面不存在
return autoreleaseNoPage(obj);
}
}
3. 自動(dòng)釋放池內(nèi)存結(jié)構(gòu)
在 ARC 模式下,是無(wú)法手動(dòng)調(diào)用 autorelease
锻全,所以要將項(xiàng)目切換至 MRC 模式 Build Settings -> Objective-C Automatic Reference Counting
設(shè)置為 NO
- 在
main.m
中添加如下代碼
// 打印自動(dòng)釋放池結(jié)構(gòu)
extern void _objc_autoreleasePoolPrint(void);
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] autorelease];
}
//調(diào)用
_objc_autoreleasePoolPrint();
}
}
運(yùn)行項(xiàng)目,打印結(jié)果如下
從打印結(jié)果我們看到有 6 個(gè)對(duì)象鳄厌,但是我們壓棧的對(duì)象是 5 個(gè)荞胡,另一個(gè)其實(shí)是前面說(shuō)到的哨兵對(duì)象(邊界),目的是為了防止越界了嚎。另外泪漂,從地址的打印,我們也看到了哨兵對(duì)象與首地址相差了 0x38
(十進(jìn)制 56)剛好就是 AutoreleasePoolPage
(繼承自 AutoreleasePoolPageData
) 所占的內(nèi)存大小歪泳。
- 將上述的 for 循環(huán)改為 505次萝勤,再次運(yùn)行項(xiàng)目,查看它的打印結(jié)果
從打印結(jié)果可以看到呐伞,第一頁(yè)已經(jīng)存滿了敌卓,存儲(chǔ)了 504 個(gè) 需要釋放的對(duì)象,第二頁(yè)存儲(chǔ)了一個(gè)對(duì)象伶氢,如果我們將 for 循環(huán)次數(shù)改為 1010 個(gè)呢趟径?
通過(guò)運(yùn)行發(fā)現(xiàn)瘪吏,第一頁(yè)存儲(chǔ) 504 個(gè),第二頁(yè)存儲(chǔ) 505 個(gè)舵抹,第三頁(yè)存儲(chǔ) 1 個(gè)
自動(dòng)釋放池第一頁(yè)可以存放 1 個(gè)哨兵對(duì)象(有且只有一個(gè),且在第一頁(yè))加 504 個(gè)需要釋放的對(duì)象劣砍,當(dāng)一頁(yè)壓棧滿了惧蛹,就會(huì)開(kāi)辟新的一頁(yè),從第二頁(yè)開(kāi)始可以存放最多 505 個(gè)對(duì)象(一頁(yè)的大小為 505*8 = 4040 字節(jié))
同樣這個(gè)結(jié)論可以通過(guò) AutoreleasePoolPage
中 SIZE
來(lái)驗(yàn)證刑枝,定義中 PAGE_MIN_SIZE
大小為 4096 字節(jié)香嗓,在其構(gòu)造函數(shù)中對(duì)象的壓棧位置 begin()
是從 首地址+56
字節(jié)開(kāi)始的,所以在一個(gè) page 中實(shí)際可以存儲(chǔ) 4096-56 = 4040
字節(jié)装畅,轉(zhuǎn)換成對(duì)象 4040/8 = 505
個(gè)靠娱,因?yàn)榈谝豁?yè)有哨兵對(duì)象,最多存儲(chǔ) 504 個(gè)
objc_autoreleasePoolPop
在 objc-781
源碼中我們看到 objc_autoreleasePoolPop
實(shí)現(xiàn)源碼中有個(gè)參數(shù)掠兄,這個(gè)參數(shù)在 clang
分析中可以找到像云,在 objc_autoreleasePoolPush()
返回一個(gè) atautoreleasepoolobj
(哨兵對(duì)象),即 ctxt
蚂夕。其目的是為了避免出椦肝埽混亂。
- 進(jìn)入
pop
源碼婿牍,實(shí)現(xiàn)如下
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
// 判斷對(duì)象是否是空占位符
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// 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 的起始位置
page = coldPage();
token = page->begin();
} else {
// 獲取 token 所在的頁(yè)
page = pageForPointer(token);
}
stop = (id *)token;
// 判斷是否是哨兵對(duì)象
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// 如果是當(dāng)前頁(yè)的第一個(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 {
// 否則上遥,返回混亂
// 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);
}
- 空白頁(yè)處理/根據(jù)token獲取page
- 容錯(cuò)處理
- popPage 出棧
進(jìn)入 popPage
的源碼搏屑。通過(guò) releaseUntil
出棧當(dāng)前頁(yè) stop
位置之前的所有對(duì)象,即向棧中的對(duì)象發(fā)送 release
消息粉楚,直到遇到傳入的哨兵對(duì)象睬棚。
template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
// 當(dāng)前操作頁(yè)面出棧
page->releaseUntil(stop);
// memory: delete empty children
// 特殊情況處理 allowDebug 傳入為 fasle
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
// 獲取當(dāng)前頁(yè)面的父節(jié)點(diǎn)
AutoreleasePoolPage *parent = page->parent;
// 將當(dāng)前頁(yè)面 kill
page->kill();
// 設(shè)置父頁(yè)面為當(dāng)前操作頁(yè)
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();
}
}
}
進(jìn)入 releaseUntil
實(shí)現(xiàn),源碼如下
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解幼,不等于抑党,一直 while 循環(huán)
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
// 獲取當(dāng)前操作頁(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)
page = page->parent;
// 將父節(jié)點(diǎn)設(shè)置為當(dāng)前操作頁(yè)
setHotPage(page);
}
page->unprotect();
// next 進(jìn)行 -- 操作,出棧
id obj = *--page->next;
// 將已開(kāi)辟內(nèi)存空間 page->next 的首 sizeof(*page->next) 個(gè)字節(jié)的值設(shè)為值 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
}
- 通過(guò)
do-while
循環(huán)底靠,判斷對(duì)象是否等于stop
,目的是釋放stop
之前的所有需要釋放的對(duì)象
- 通過(guò)
- 判空處理
- 通過(guò)獲取 page 的 next 對(duì)象特铝,標(biāo)記已被釋放狀態(tài)
- 判斷是否是哨兵對(duì)象暑中,如果不是則自動(dòng)調(diào)用
objc_release
釋放
- 判斷是否是哨兵對(duì)象暑中,如果不是則自動(dòng)調(diào)用
下面我們進(jìn)入 kill
的源碼壹瘟,實(shí)現(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;
// 獲取最后一個(gè) page
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
// 當(dāng)前頁(yè)的子節(jié)點(diǎn)變?yōu)楦腹?jié)點(diǎn)
page = page->parent;
if (page) {
page->unprotect();
// 將父節(jié)點(diǎn)頁(yè)的子節(jié)點(diǎn)變?yōu)?nil
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
主要是銷(xiāo)毀當(dāng)前頁(yè),將當(dāng)前頁(yè)賦值為父節(jié)點(diǎn)頁(yè)鳄逾,并將父節(jié)點(diǎn)頁(yè)的 child
對(duì)象指針置為 nil
autorelease 源碼分析
上面我們知道了 autoreleasepool
的底層原理稻轨,下面我們來(lái)看下 autorelease
的底層實(shí)現(xiàn),如下雕凹,我們打開(kāi)匯編看下 autorelease
的底層調(diào)用(因?yàn)?Xcode 默認(rèn)是 ARC 模式殴俱,不能調(diào)用 autorelease
,需要開(kāi)啟 MRC 模式)
打開(kāi) objc781
源碼枚抵,我們進(jìn)入 objc_autorelease
的源碼线欲,如下
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
// 如果不是對(duì)象,直接返回
if (!obj) return obj;
// 如果是 Tagged Pointer汽摹,直接返回
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
繼續(xù)往下走
inline id
objc_object::autorelease()
{
ASSERT(!isTaggedPointer());
// 判斷是否是自定義類(lèi)
if (fastpath(!ISA()->hasCustomRR())) {
return rootAutorelease();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
}
inline id
objc_object::rootAutorelease()
{
// 如果是 Tagged Pointer李丰,返回
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());
// 壓棧操作
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
無(wú)論是壓棧哨兵對(duì)象,還是普通對(duì)象逼泣,都會(huì)來(lái)到
autoreleaseFast
方法趴泌,只是區(qū)別標(biāo)識(shí)不同而以
總結(jié)
通過(guò)以上的分析,針對(duì)自動(dòng)釋放池的 push
以及 pop
拉庶,做個(gè)總結(jié)
objc_autoreleasePoolPush
- 判斷有沒(méi)有
pool
踱讨,即只有空占位符(存儲(chǔ)在tls中)時(shí),創(chuàng)建頁(yè)砍的,并壓棧哨兵對(duì)象
- 判斷有沒(méi)有
- 壓棧對(duì)象痹筛,通過(guò)
page->add(obj)
方法,將next
指針遞增
- 壓棧對(duì)象痹筛,通過(guò)
- 當(dāng)頁(yè)面滿了廓鞠,設(shè)置當(dāng)前操作頁(yè)的
child
為新建頁(yè)帚稠,并設(shè)置新建頁(yè)為當(dāng)前操作頁(yè),壓棧對(duì)象
- 當(dāng)頁(yè)面滿了廓鞠,設(shè)置當(dāng)前操作頁(yè)的
push
流程圖
objc_autoreleasePoolPop
- 在頁(yè)中出棧床佳,主要是通過(guò)
next
指針遞減實(shí)現(xiàn)
- 在頁(yè)中出棧床佳,主要是通過(guò)
- 當(dāng)頁(yè)空了時(shí)滋早,賦值頁(yè)的
parent
為當(dāng)前操作頁(yè),并將新的當(dāng)前操作頁(yè)的child
設(shè)置為 nil
- 當(dāng)頁(yè)空了時(shí)滋早,賦值頁(yè)的
pop
流程圖