AutoReleasePoolPage
類(lèi)的定義
class AutoreleasePoolPage
{
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3;
static size_t const SIZE = PAGE_MAX_SIZE;
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
成員變量解析:
- key:用于創(chuàng)建線(xiàn)程的標(biāo)識(shí)
- SCRIBBLE:刷子秀菱,可以不需要關(guān)心
- SIZE:4096Bytes
- COUNT:4096/8
- magic:用于驗(yàn)證
AutoReleasePoolPage
的完整性 - next:棧頂指針
- thread: 當(dāng)前線(xiàn)程
- parent: 父節(jié)點(diǎn),
AutoReleasePoolPage
是一個(gè)雙向鏈表次员,這個(gè)parent
也就是指向上一個(gè)節(jié)點(diǎn) - child: 子節(jié)點(diǎn)(下一個(gè)節(jié)點(diǎn))
- depth: 深度,標(biāo)識(shí)這是第幾個(gè)節(jié)點(diǎn),從0開(kāi)始計(jì)數(shù)
- hiwat: high water mark 高水位線(xiàn)图呢,用來(lái)報(bào)警內(nèi)存占用
就如前面所說(shuō),AutoReleasePoolPage
他是一個(gè)雙向鏈表骗随,每個(gè)節(jié)點(diǎn)大小為4096Bytes蛤织,而且前面56Bytes是成員函數(shù)所占有的。而且每個(gè)節(jié)點(diǎn)中有一個(gè)堆棧鸿染,其中next
指針是指向棧頂指蚜。
begin()
和end()
兩個(gè)方法分辨指向棧底和對(duì)象的結(jié)尾:
begin
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
end
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
標(biāo)準(zhǔn)的進(jìn)棧操作
add
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next;
*next++ = obj;
protect();
return ret;
}
出棧
releaseUntil
void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
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
}
這里的出棧跟普通出棧不太一樣,因?yàn)檫@里的出棧會(huì)一直遞歸出棧到stop
才會(huì)停止涨椒,這里的SCRIBBLE
就是用來(lái)填寫(xiě)空白內(nèi)存區(qū)的摊鸡。
以上基本上算這個(gè)類(lèi)的定義內(nèi)容的解釋。
那么這個(gè)類(lèi)有什么作用呢蚕冬?
用來(lái)管理實(shí)現(xiàn)垃圾自動(dòng)回收免猾。
怎么實(shí)現(xiàn)垃圾回收?
void autoReleaseTest(void)
{
void *pool = objc_autoreleasePoolPush();
........
objc_autoreleasePoolPop(pool);
}
每次有一個(gè)加一個(gè)AutoReleasePool
囤热,編譯器自動(dòng)回報(bào)我們解析成以上的形式猎提。那么看一下objc_autoreleasePoolPush()
和objc_autoreleasePoolPop(pool)
方法的實(shí)現(xiàn)
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
也就是要先看看:
push
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
這里可以看到Debug
實(shí)現(xiàn)的方式不太一樣,
Debug
是每次push
都會(huì)創(chuàng)建一個(gè)新的page
然而非Debug
卻不是
這應(yīng)該是為了Release
節(jié)省內(nèi)存設(shè)計(jì)的旁蔼。
所以Debug
環(huán)境會(huì)比Release
環(huán)境內(nèi)存耗費(fèi)的多一點(diǎn)吧锨苏。
至于這里的POOL_BOUNDARY
是被define
成一個(gè)nil
對(duì)象的,用處就是一個(gè)哨兵對(duì)象的作用棺聊。
說(shuō)一下哨兵的作用:
哨兵節(jié)點(diǎn)通常被用在鏈表和遍歷樹(shù)中伞租,他并不擁有或引用任何被數(shù)據(jù)結(jié)構(gòu)管理的數(shù)據(jù)。常常用哨兵節(jié)點(diǎn)來(lái)替代null
限佩,這樣的好處有:
- 增加操作的速度
- 降低算法的復(fù)雜性和代碼的大小
- 增加數(shù)據(jù)結(jié)構(gòu)的魯棒性
簡(jiǎn)單的說(shuō)哨兵就是為了簡(jiǎn)化邊界條件的處理而存在的葵诈。
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);
}
}
根據(jù)不同的page
狀態(tài)來(lái)進(jìn)行不同的操作
- 當(dāng)
page
存在且未滿(mǎn)的時(shí)候:直接add object
- 當(dāng)
page
存在但是已經(jīng)滿(mǎn)了的時(shí)候:先創(chuàng)建一個(gè)page
,且當(dāng)前page
的child
指向新page
祟同,新page
的parent
指向當(dāng)前page
作喘,然后add object
- 當(dāng)
page
不存在的時(shí)候:創(chuàng)建一個(gè)新page
,再add object
autoreleaseFullPage
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *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);
}
hotpage()
這個(gè)是當(dāng)前正在使用的page
autoreleaseNoPage
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
return setEmptyPoolPlaceholder();
}
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
return page->add(obj);
}
add
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next;
*next++ = obj;
protect();
return ret;
}
這里的next是指向一個(gè)空指針的耐亏,注意一下徊都!
以上就是push操作了。
pop
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
if (hotPage()) {
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
} else {
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
if (DebugPoolAllocation && page->empty()) {
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
page->kill();
setHotPage(nil);
}
else if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
pop
操作比push
要麻煩一些广辰!
簡(jiǎn)單的說(shuō)就是:
- 找到
token
所在的page
- 對(duì)
token
之后的所有對(duì)象暇矫,發(fā)送release消息 - 殺掉多余的
page
pageForPointer
static AutoreleasePoolPage *pageForPointer(const void *p)
{
return pageForPointer((uintptr_t)p);
}
static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;
assert(offset >= sizeof(AutoreleasePoolPage));
result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();
return result;
}
- 先獲得偏移量
- 通過(guò)當(dāng)前
p
減去偏移量獲取page
指針 - 檢測(cè)完整性
- 返回
page
releaseUntil
void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
- 獲取
hotpage
- 判斷是否為空,非空進(jìn)入下一步择吊,為空獲取
parent
李根,并且設(shè)置parent
為hotpage
,然后進(jìn)入上一步 - 獲取棧頂對(duì)象,清空棧頂指針几睛,向棧頂對(duì)象發(fā)送
release
房轿,下移next
指針 - 判斷
next
是否等于stop
,相等進(jìn)入下一步,不相等回到第一步 - 設(shè)置
hotpage
在debug環(huán)境中因?yàn)槊看?code>push會(huì)創(chuàng)建一個(gè)新的page
囱持,每次pop
會(huì)把空的page
kill掉夯接,所以不存在空的page
。
kill
if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
- 當(dāng)
page
使用量小于一半纷妆,殺掉page
的child
之后的所有page
- 當(dāng)
page
的使用量大于一半且存在孫子的時(shí)候盔几,殺掉孫子之后的所有page
- 當(dāng)
page
的使用量大于一半且不存在孫子的時(shí)候,保留child
這里為什么要保留一個(gè)child
,可能大家覺(jué)得很奇怪,其實(shí)是為了節(jié)省開(kāi)銷(xiāo)掩幢,因?yàn)槿绻笥谝话氲臈1徽加昧酥蠛苡锌赡苎放模R上需要新建新的page
對(duì)象。
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;
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);
}
- 先找到雙向鏈表最后一個(gè)節(jié)點(diǎn)
- 然后從最后一個(gè)節(jié)點(diǎn)開(kāi)始刪除
- 直到刪除到當(dāng)前節(jié)點(diǎn)(包括當(dāng)前節(jié)點(diǎn))
總結(jié)
那么到目前為止AutoReleasePoolPage
里面主要的流程已經(jīng)講完了际邻。其他的一些方法都是無(wú)關(guān)大雅的東西了芯丧。
實(shí)際上AutoReleasePoolPage
只是做一個(gè)導(dǎo)師的角色,他并不會(huì)去銷(xiāo)毀對(duì)象(自己除外)世曾,但是他回去告訴他管理的對(duì)象現(xiàn)在該release
一下缨恒。
有問(wèn)題的地方望各位大能指出來(lái)!看到我會(huì)改正的哦度硝!~