前言
在互聯(lián)網(wǎng)時(shí)代蚣旱,電子設(shè)備的內(nèi)存管理是一個(gè)困擾的技術(shù)難點(diǎn)之一果复。隨著iPhone手機(jī)技術(shù)的更新叙淌,在2011年之前使用手動(dòng)引用計(jì)數(shù)MRC(Manual Reference Counting)互亮,在WWDC2011和iOS 5 引入了自動(dòng)引用計(jì)數(shù)ARC(Auto Reference Counting)慢宗,一個(gè)全新的內(nèi)存管理機(jī)制誕生坪蚁。而autoreleasepool是OC內(nèi)存管理機(jī)制,在ARC的機(jī)制下會(huì)經(jīng)常使用到@autoreleasepool自動(dòng)釋放池管理镜沽、優(yōu)化內(nèi)存敏晤。
一、基本概念
ARC下的產(chǎn)物淘邻,為了替代人工管理內(nèi)存茵典,大大的簡(jiǎn)化了iOS開(kāi)發(fā)人員的內(nèi)存管理工作;實(shí)質(zhì)上是使用編譯器替代人工在適當(dāng)?shù)奈恢貌迦雛elease宾舅、autorelease等內(nèi)存釋放操作统阿;
@autoreleasepool 自動(dòng)釋放池:
管理內(nèi)存的池,把不需要的對(duì)象放在自動(dòng)釋放池中筹我,自動(dòng)釋放這個(gè)池子內(nèi)的對(duì)象扶平。(簡(jiǎn)單,接下來(lái)會(huì)詳細(xì)說(shuō)明@autoreleasepool工作過(guò)程)
二蔬蕊、底層結(jié)構(gòu)
在ARC中结澄,看一下@autoreleasepool底層代碼具體是什么。
1.查看@autoreleasepool{ }編譯成C++代碼
使用編譯器clang編譯main.m轉(zhuǎn)化成main.cpp文件(在終端:clang -rewrite-objc main.m)
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
}
}
編譯之后的main.cpp的代碼,把主要的代碼拷貝出來(lái)如下
extern "C" __declspec(dllimport)
void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport)
void objc_autoreleasePoolPop(void *);
struct __AtAutoreleasePool {
__AtAutoreleasePool()
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool(){
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
可以從以上代碼看出來(lái)@autoreleasepool其實(shí)是objc_autoreleasePoolPush 和 objc_autoreleasePoolPop這兩個(gè)方法組成的麻献。
總之:@autoreleasepool是由objc_autoreleasePoolPush 和 objc_autoreleasePoolPop方法構(gòu)成的一個(gè)結(jié)構(gòu)體们妥。
2.查看objc_autoreleasePoolPush和objc_autoreleasePoolPop
參照蘋(píng)果開(kāi)源代碼找到objc_autoreleasePoolPush和objc_autoreleasePoolPop兩個(gè)方法,兩個(gè)方法在NSObject.mm中實(shí)現(xiàn)(蘋(píng)果開(kāi)源代碼:https://opensource.apple.com/tarballs/objc4/)
void *objc_autoreleasePoolPush(void){
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt){
if (UseGC) return; // fixme rdar://9167170
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
從上面可以發(fā)現(xiàn)勉吻,C++類(lèi)AutoreleasePoolPage才是實(shí)際的實(shí)現(xiàn)所在监婶,找到AutoreleasePoolPage:
class AutoreleasePoolPage
{
#define POOL_SENTINEL nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif // 通過(guò)查詢(xún) PAGE_MAX_SIZE = 4096
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic; // 驗(yàn)證碼
id *next; //棧頂?shù)刂? pthread_t const thread; // 所屬線(xiàn)程
AutoreleasePoolPage * const parent; //父節(jié)點(diǎn)
AutoreleasePoolPage *child; // 子節(jié)點(diǎn)
uint32_t const depth; //page的復(fù)雜度
uint32_t hiwat;
……
去除了一些不重要的代碼,可以看出這是一個(gè)典型的雙向列表結(jié)構(gòu)齿桃,每個(gè)Page大小為4096 Byte惑惶,所以AutoreleasePool實(shí)質(zhì)上是一個(gè)雙向AutoreleasePoolPage列表;接下來(lái)分析一下自動(dòng)釋放池的工作過(guò)程:
3.創(chuàng)建自動(dòng)釋放池
void* objc_autoreleasePoolPush()內(nèi)部實(shí)際調(diào)用的是AutoreleasePoolPage::push()函數(shù)短纵,其實(shí)現(xiàn)如下:
// objc_autoreleasePoolPush()內(nèi)部實(shí)際調(diào)用
void * objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
// AutoreleasePoolPage::push()的 push方法
static inline void *push()
{
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) { // 如果頁(yè)面不為空并且存在
return page->add(obj); // 添加對(duì)象到自動(dòng)釋放池入棧
} else if (page) { // 如果自動(dòng)釋放池頁(yè)存在 且 頁(yè)面滿(mǎn)了
return autoreleaseFullPage(obj, page); //
} else { //
return autoreleaseNoPage(obj);
}
}
從以上開(kāi)源代碼可以看出带污,hotPage()是找出當(dāng)前的正在使用的page
1.hotPage存在且未滿(mǎn),AutoreleasePoolPage對(duì)象作為自動(dòng)釋放池加入棧中
2.hotPage存在且hotPage頁(yè)面滿(mǎn)了香到,AutoreleasePoolPage創(chuàng)建新的Page并把對(duì)象添加到棧中
3.hotPage不存在鱼冀。添加一個(gè)新的AutoreleasePoolPage頁(yè)面添加對(duì)象
3.1.hotPage不存在,執(zhí)行的方法
id *autoreleaseNoPage(id obj)
{
// No pool in place.
assert(!hotPage());
if (obj != POOL_SENTINEL && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
(void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push an autorelease pool boundary if it wasn't already requested.
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
// Push the requested object.
return page->add(obj);
}
3.2.hotPage存在且hotPage頁(yè)面滿(mǎn)悠就,執(zhí)行的方法
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() && page->full());
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
3.3.hotPage存在且未滿(mǎn)雷绢,執(zhí)行添加對(duì)象
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
綜上可以看出在添加自動(dòng)釋放池,所有操作都是對(duì)雙向堆棧AutoreleasePoolPage的一個(gè)創(chuàng)建和添加的操作理卑。
4.銷(xiāo)毀自動(dòng)釋放池
首先autoreleasepool的釋放工作交給objc_autoreleasePoolPop方法,bjc_autoreleasePoolPop方法如下蔽氨,自動(dòng)釋放主要交給AutoreleasePoolPage::pop(ctxt);進(jìn)行
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
// fixme rdar://9167170
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
自動(dòng)釋放的方法如下藐唠,更具傳入的token,查找需要?jiǎng)h除的那個(gè)頁(yè)面鹉究,進(jìn)行刪除操作宇立。
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token) {
page = pageForPointer(token);
stop = (id *)token;
assert(*stop == POOL_SENTINEL);
} else {
// Token 0 is top-level pool
page = coldPage();
assert(page);
stop = page->begin();
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
// hysteresis: keep one empty child if this page is more than half full
// special case: delete everything for pop(0)
// special case: delete everything for pop(top) with DebugMissingPools
if (!token ||
(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();
}
}
}
釋放自動(dòng)釋放池內(nèi)內(nèi)存,雙向堆棧中自赔,刪除一個(gè)AutoreleasePoolPage妈嘹,根據(jù)這個(gè)AutoreleasePoolPage對(duì)象找到,通過(guò)while循環(huán)找到AutoreleasePoolPage下方的對(duì)象绍妨,就像二叉樹(shù)找到葉子節(jié)點(diǎn)润脸。通過(guò)節(jié)點(diǎn),首先記錄這個(gè)節(jié)點(diǎn)的地址他去,找出這個(gè)節(jié)點(diǎn)的父節(jié)點(diǎn)毙驯。通過(guò)父節(jié)點(diǎn)把子節(jié)點(diǎn)置空,刪除這個(gè)節(jié)點(diǎn)的指針指向灾测,在通過(guò)delete刪除對(duì)象A的內(nèi)存空間爆价。通過(guò)while循環(huán),直到刪除到最初的節(jié)點(diǎ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;
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);
}
總結(jié)
看到這里铭段,相信你應(yīng)該對(duì) Objective-C 的內(nèi)存管理機(jī)制有了更進(jìn)一步的認(rèn)識(shí)骤宣。通常情況下,我們是不需要手動(dòng)添加 autoreleasepool 的序愚,使用線(xiàn)程自動(dòng)維護(hù)的 autoreleasepool 就好了憔披。根據(jù)蘋(píng)果官方文檔中對(duì) Using Autorelease Pool Blocks 的描述,我們知道在下面三種情況下是需要我們手動(dòng)添加 autoreleasepool 的:
- 如果你編寫(xiě)的程序不是基于 UI 框架的展运,比如說(shuō)命令行工具活逆;
- 如果你編寫(xiě)的循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象;
- 如果你創(chuàng)建了一個(gè)輔助線(xiàn)程拗胜。