引用計數(shù)
在Objective-C
內(nèi)存管理中,每個對象都有屬于自己的計數(shù)器捣染;如果想讓某個對象繼續(xù)存活,就增加它的引用計數(shù)停巷;當用完它之后耍攘,就減少該計數(shù);當沒人引用該對象畔勤,它的計數(shù)變?yōu)?之后蕾各,系統(tǒng)就把它銷毀。
所以庆揪,在objective-C
的內(nèi)存管理中式曲,關(guān)鍵就在于對象釋放的時機,autorelease
的妙處在于缸榛,它找到了一個合適的時機來釋放返回對象吝羞,這個時機就是本次消息循環(huán)結(jié)束的時候。我們只需要在返回對象前内颗,調(diào)用autorelease
钧排,對象被加入autorelease pool
(但沒有減少對象的引用計數(shù),所以這時候返回的對象仍是有效的)起暮,然后返回卖氨,程序繼續(xù)執(zhí)行,直到完成本次消息循環(huán)之時负懦,再把autorelease pool
中記錄的臨時對象一個個分別release
--減少引用計數(shù)筒捺。
AutoreleasePool
App啟動后,蘋果在主線程RunLoop
里注冊了兩個Observer
纸厉,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
系吭。
第一個Observer
監(jiān)視的事件是Entry
(即將進入Loop),其回調(diào)內(nèi)會調(diào)用_objc_autoreleasePoolPush()
創(chuàng)建自動釋放池颗品。其order
是-2147483647肯尺,優(yōu)先級最高沃缘,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個Observer
監(jiān)視了兩個事件: BeforeWaiting
(準備進入休眠) 時調(diào)用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
釋放舊的池并創(chuàng)建新池则吟;Exit(即將退出Loop) 時調(diào)用_objc_autoreleasePoolPop()
來釋放自動釋放池槐臀。這個Observer
的order
是 2147483647,優(yōu)先級最低氓仲,保證其釋放池子發(fā)生在其他所有回調(diào)之后水慨。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)敬扛、Timer回調(diào)內(nèi)的晰洒。這些回調(diào)會被RunLoop
創(chuàng)建好的AutoreleasePool
環(huán)繞著,所以不會出現(xiàn)內(nèi)存泄漏啥箭,開發(fā)者也不必顯示創(chuàng)建Pool
了谍珊。
Autorelease原理
@autoreleasepool{ }
使用clang編譯器把main.m
轉(zhuǎn)化成main.cpp
文件,查看一下@autoreleasepool{ }
的C++
源碼急侥,可以看到@autoreleasepool{ }
其實很簡單砌滞,是一個__AtAutoreleasePool
結(jié)構(gòu)體,這個結(jié)構(gòu)體的構(gòu)造也很簡單缆巧,一個構(gòu)造函數(shù)和一個析構(gòu)函數(shù)布持,構(gòu)造函數(shù)中通過調(diào)用objc_autoreleasePoolPush()
函數(shù)來實現(xiàn)構(gòu)造豌拙;析構(gòu)函數(shù)通過調(diào)用objc_autoreleasePoolPop()
函數(shù)來實現(xiàn)析構(gòu)陕悬。
OC
代碼:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc]init];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
C++
代碼:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
AutoreleasePoolPage
objc_autoreleasePoolPush()
和objc_autoreleasePoolPop()
的函數(shù)實現(xiàn)我們可以在蘋果開源代碼objc4的NSObject.mm
中找到,其實就是直接對AutoreleasePoolPage
的調(diào)用按傅。
push
- AutoreleasePool并沒有單獨的結(jié)構(gòu)捉超,而是由若干個AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針)
- AutoreleasePool是按線程一一對應(yīng)的(結(jié)構(gòu)中的thread指針指向當前線程)
- AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大小)唯绍,除了上面的實例變量所占空間拼岳,剩下的空間全部用來儲存autorelease對象的地址
- 上面的id *next指針作為游標指向棧頂最新add進來的autorelease對象的下一個位置
- 一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象况芒,連接鏈表惜纸,后來的autorelease對象在新的page加入
class AutoreleasePoolPage
{
magic_t const magic; // 用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整
id *next; // 指向棧頂,也就是最新入棧的autorelease對象的下一個位置
pthread_t const thread; // 指向當前線程
AutoreleasePoolPage * const parent; // 指向父節(jié)點
AutoreleasePoolPage *child; // 指向子節(jié)點
uint32_t const depth; // 表示鏈表的深度绝骚,也就是鏈表節(jié)點的個數(shù)
uint32_t hiwat; // 表示high water mark(最高水位標記)
static inline void *push() {
id *dest;
if (DebugPoolAllocation) { // Debug模式
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
static inline id *autoreleaseFast(id obj) {
AutoreleasePoolPage *page = hotPage(); // 獲取當前PoolPage
if (page && !page->full()) { // 當前Page存在且未滿
return page->add(obj); // 把obj添加到當前Page中
} else if (page) { // 當前Page已滿
return autoreleaseFullPage(obj, page);
} else { // 不存在PoolPage
return autoreleaseNoPage(obj);
}
}
}
pop
每當進行一次objc_autoreleasePoolPush
調(diào)用時耐版,runtime
向當前的AutoreleasePoolPage
中add
進一個哨兵對象
,值為0(也就是個nil)压汪,那么這一個page就變成了下面的樣子:
objc_autoreleasePoolPush
的返回值正是這個哨兵對象的地址粪牲,被objc_autoreleasePoolPop(哨兵對象)
作為入?yún)ⅲ谑牵?/p>
- 根據(jù)傳入的哨兵對象地址找到哨兵對象所處的
page
- 在當前
page
中止剖,將晚于哨兵對象插入的所有autorelease
對象都發(fā)送一次- release
消息腺阳,并向回移動next
指針到正確位置- 補充2:從最新加入的對象一直向前清理落君,可以向前跨越若干個
page
,直到哨兵所在的page
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // 是否占位池
// Popping the top-level placeholder pool.
// 彈出頂層占位池
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
// 使用了游泳池亭引, 正常彈出內(nèi)容
// 池仍像往常一樣分配給重用
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
// 游泳池從未使用 清除占位符
setHotPage(nil);
}
return;
}
page = pageForPointer(token); // 通過哨兵地址獲取哨兵對象所在page對象
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// 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 (PrintPoolHiwat) printHiwat(); // 記錄最高水位標記
page->releaseUntil(stop); // 把哨兵對象之后入棧的對象進行出棧操作绎速,并對出棧對象發(fā)出release消息
// memory: delete empty children
// // 刪除空掉的page
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
// 在Debug期間進行刪除操作
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
// 在不存在autorelease pools的情況刪除空的page
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
// 如果當前page使用超過一半,就保留一個空的child page
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}