一泊交、Autorelease Pool是什么
AutoreleasePool(自動(dòng)釋放池)
是OC中的一種內(nèi)存自動(dòng)回收機(jī)制
臭觉,它可以延遲
加入AutoreleasePool
中的變量release
的時(shí)機(jī)。在正常情況下辨液,創(chuàng)建的變量
會(huì)在超出其作用域
的時(shí)候release
虐急,但是如果將變量加入AutoreleasePool
,那么release
將延遲
執(zhí)行滔迈。
二止吁、Autorelease對(duì)象到底什么時(shí)候被釋放
很多人說(shuō)在“當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放
”,顯然沒(méi)有正確理解Autorelease機(jī)制
燎悍。
在沒(méi)有手加 Autorelease Pool
的情況下敬惦,Autorelease
對(duì)象是在當(dāng)前的 runloop迭代結(jié)束
時(shí)釋放的,而它能夠釋放的原因是系統(tǒng)在每個(gè) runloop
迭代中都加入了 自動(dòng)釋放池Push和Pop
三谈山、在
MRC
中俄删,調(diào)用[obj autorelease]
來(lái)延遲內(nèi)存的釋放是一件簡(jiǎn)單自然的事,ARC
下奏路,我們甚至可以完全不知道Autorelease
就能管理好內(nèi)存畴椰。而在這背后,objc和編譯器
都幫我們做了哪些事呢?
如下Demo:
#import <Foundation/Foundation.h>
// 生成兩個(gè)全局weak變量用來(lái)觀察實(shí)驗(yàn)對(duì)象
__weak NSString *weak_String;
__weak NSString *weak_StringAutorelease;
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
[self createStr];
NSLog(@"------in the autoreleasepool------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
NSLog(@"------in the main()------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
-(void)createStr
{
// 創(chuàng)建常規(guī)對(duì)象
NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];
// 創(chuàng)建autorelease對(duì)象
NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"];
weak_String = string;
weak_StringAutorelease = stringAutorelease;
NSLog(@"------in the createString()------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
運(yùn)行結(jié)果如下:
- 首先在
createString
函數(shù)中創(chuàng)建了一個(gè)常規(guī)NSString對(duì)象
和一個(gè)autorelease對(duì)象
鸽粉,然后分別賦值給兩個(gè)weak全局變量
用于觀察目標(biāo)對(duì)象斜脂。通過(guò)打印結(jié)果可以看到,在createString
函數(shù)中兩個(gè)對(duì)象都是正常存在的触机,出了createString
函數(shù)在autoreleasepool
中帚戳,常規(guī)對(duì)象
已經(jīng)被釋放玷或,而autorelease對(duì)象
依然存在。在autoreleasepool
外片任,autorelease對(duì)象
也被釋放了庐椒。
四、AutoreleasePool 的底層實(shí)現(xiàn)
- 蘋果通過(guò)聲明一個(gè)
__AtAutoreleasePool
類型的局部變量__autoreleasepool
實(shí)現(xiàn)了@autoreleasepool{}
蚂踊。 - 看看
__AtAutoreleasePool
的定義:
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;
};
我們可以分析出约谈,單個(gè)自動(dòng)釋放池的執(zhí)行過(guò)程就是:
objc_autoreleasePoolPush()
—> [object autorelease]
—> objc_autoreleasePoolPop(void *)
五、AutoreleasePoolPage
在objc_autoreleasePoolPush()
函數(shù)的實(shí)現(xiàn):
void *
objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
同樣objc_autoreleasePoolPop()
函數(shù)的實(shí)現(xiàn):
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
我們發(fā)現(xiàn)這兩個(gè)函數(shù)的實(shí)現(xiàn)都是調(diào)用了AutoreleasePoolPage類
中的方法犁钟。于是我們可以斷定棱诱,AutoreleasePool
是通過(guò)AutoreleasePoolPage類
來(lái)實(shí)現(xiàn)的。
看看AutoreleasePoolPage
定義的屬性:
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
1涝动、AutoreleasePool
并沒(méi)有單獨(dú)的結(jié)構(gòu)迈勋,而是由若干個(gè)AutoreleasePoolPage以雙向鏈表
的形式組合而成(分別對(duì)應(yīng)結(jié)構(gòu)中的parent指針
和child指針
)
2、AutoreleasePool
是按線程
一一對(duì)應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
3醋粟、AutoreleasePoolPage
每個(gè)對(duì)象會(huì)開辟4096字節(jié)
內(nèi)存(也就是虛擬內(nèi)存一頁(yè)的大忻夜健),除了上面的實(shí)例變量所占空間米愿,剩下的空間全部用來(lái)儲(chǔ)存autorelease對(duì)象
的地址
4厦凤、上面的id *next指針
作為游標(biāo)指向棧頂
最新add進(jìn)來(lái)的autorelease對(duì)象
的下一個(gè)位置,一個(gè)AutoreleasePoolPage
的空間被占滿
時(shí)育苟,會(huì)新建一個(gè)AutoreleasePoolPage對(duì)象
较鼓,連接鏈表,后來(lái)的autorelease對(duì)象
在新的page
加入
5违柏、棧中存放的指針指向加入需要release
的對(duì)象或者POOL_SENTINEL
(哨兵對(duì)象博烂,用于分隔autoreleasepool)。
6漱竖、棧中指向POOL_SENTINEL
的指針就是autoreleasepool
的一個(gè)標(biāo)記禽篱。當(dāng)autoreleasepool
進(jìn)行出棧
操作,每一個(gè)比這個(gè)哨兵對(duì)象后進(jìn)棧
的對(duì)象都會(huì)被release
馍惹。
- 當(dāng) next == begin() 時(shí)躺率,表示 AutoreleasePoolPage 為空;
當(dāng) next == end() 時(shí)讼积,表示 AutoreleasePoolPage 已滿肥照。
下面來(lái)看看實(shí)現(xiàn)AutoreleasePool
的幾個(gè)關(guān)鍵函數(shù)是如何實(shí)現(xiàn)的
AutoreleasePoolPage::push()
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 區(qū)別調(diào)試模式
// Each autorelease pool starts on a new pool page.
// 調(diào)試模式下將新建一個(gè)鏈表節(jié)點(diǎn),并將一個(gè)哨兵對(duì)象添加到鏈表?xiàng)V? dest = autoreleaseNewPage(POOL_SENTINEL);
} else {
dest = autoreleaseFast(POOL_SENTINEL); // 添加一個(gè)哨兵對(duì)象到自動(dòng)釋放池的鏈表?xiàng)V? }
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 獲取最新的page(即鏈表上最新的節(jié)點(diǎn))
if (page && !page->full()) {
return page->add(obj); // 在這個(gè)page存在且不滿的情況下勤众,直接將需要autorelease的對(duì)象加入棧中
} else if (page) {
return autoreleaseFullPage(obj, page); // 在這個(gè)page已經(jīng)滿了的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
} else {
return autoreleaseNoPage(obj); // 在沒(méi)有page的情況下鲤脏,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
}
}
autoreleaseFullPage(obj, page)
和autoreleaseNoPage(obj)
的區(qū)別在于autoreleaseFullPage(obj, page)
會(huì)將當(dāng)前page的child指向新建的page们颜,而autoreleaseNoPage(obj)
會(huì)在新建的page中先入棧一個(gè)POOL_SENTINEL
(哨兵對(duì)象)吕朵,再將obj入棧。
id *add(id obj) // 入棧操作
{
assert(!full());
unprotect(); // 解除保護(hù)
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 將obj入棧到棧頂并重新定位棧頂
protect(); // 添加保護(hù)
return ret;
}
AutoreleasePoolPage::pop(ctxt)
static inline void pop(void *token) // token指針指向棧頂?shù)牡刂? {
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token); // 通過(guò)棧頂?shù)牡刂氛业綄?duì)應(yīng)的page
stop = (id *)token;
if (DebugPoolAllocation && *stop != POOL_SENTINEL) {
// This check is not valid with DebugPoolAllocation off
// after an autorelease with a pool page but no pool in place.
_objc_fatal("invalid or prematurely-freed autorelease pool %p; ",
token);
}
if (PrintPoolHiwat) printHiwat(); // 記錄最高水位標(biāo)記
page->releaseUntil(stop); // 從棧頂開始操作出棧窥突,并向棧中的對(duì)象發(fā)送release消息努溃,直到遇到第一個(gè)哨兵對(duì)象
// memory: delete empty children
// 刪除空掉的節(jié)點(diǎn)
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
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
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();
}
}
}
AutoreleasePoolPage::autorelease((id)this);
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj); // 添加obj對(duì)象到自動(dòng)釋放池的鏈表?xiàng)V? assert(!dest || *dest == obj);
return obj;
}
autorelease
函數(shù)和push
函數(shù)一樣,關(guān)鍵代碼都是調(diào)用autoreleaseFast
函數(shù)向自動(dòng)釋放池的鏈表?xiàng)V刑砑右粋€(gè)對(duì)象阻问,不過(guò)push
函數(shù)的入棧的是一個(gè)哨兵對(duì)象梧税,而autorelease
函數(shù)入棧的是需要加入autoreleasepool
的對(duì)象。
六称近、補(bǔ)充
使用容器的block版本的枚舉器時(shí)第队,內(nèi)部會(huì)自動(dòng)添加一個(gè)AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這里被一個(gè)局部@autoreleasepool包圍著
}];
普通for循環(huán)和for in循環(huán)中沒(méi)有,for循環(huán)中遍歷產(chǎn)生大量autorelease變量時(shí)刨秆,就需要手加局部AutoreleasePool凳谦。
-
下面三種情況下是需要我們手動(dòng)添加 autoreleasepool 的:
1、你編寫的程序不是基于 UI 框架的衡未,比如說(shuō)命令行工具尸执;
2、你編寫的循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象缓醋;
3如失、你創(chuàng)建了一個(gè)輔助線程。
參考鏈接
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/#jtss-tsina