autoreleasepool

autorelease簡介:

autorelease是一種支持引用計數(shù)的內(nèi)存管理方式,只要給對象發(fā)送一條autorelease消息,會將對象放到一個自動釋放池中,當自動釋放池被銷毀時,會對池子里面的所有對象做一次release操作,

?使用autorelease有什么好處呢:不用再關(guān)心對象釋放的時間,不用再關(guān)心什么時候調(diào)用release

autorelesspool寫法:

1. NSAutoreleasePool*autoreleasePool = [[NSAutoreleasePoolalloc] init];

?Person *p = [[[Person alloc] init] autorelease];

?[autoreleasePool drain]; ?//池子銷毀會給池子中所有對象發(fā)送一條release消息,NSAutoreleasePool對象不能retain, 不能autorelease

2. @autoreleasepool{// 創(chuàng)建一個自動釋放池

Person *p = [[Personnew] autorelease];// 將代碼寫到這里就放入了自動釋放池

}// 銷毀自動釋放池(會給池子中所有對象發(fā)送一條release消息),在arc下自動管理。

MRC下需要對象調(diào)用autorelease才會入池, ARC下可以通過__autoreleasing修飾符, 否則的話看方法名, 非alloc/new/copy/mutableCopy開頭的初始化方法編譯器都會自動幫我們調(diào)用autorelease方法.

autorelease pool堆棧結(jié)構(gòu)

每一個autorelease pool創(chuàng)建都會將池子入棧,給池子發(fā)送drain或者release消息,該池子和其后創(chuàng)建的池子都將銷毀

例子:?

? ? NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc]init];

?? ? TestOne*one = [[[TestOnealloc]init]autorelease];

? ? NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc]init];

? ? TestTwo *two = [[[TestTwo alloc]init]autorelease];

? ? [pool1 drain];

? ? sleep(5); //阻礙pool的釋放,因為函數(shù)執(zhí)行完池子就會被銷毀,即使不發(fā)送release或drain消息

輸出:

TestTwo dealloc

TestOne dealloc

即使只使用了? [pool1 drain];one對象和two對象都能釋放

autorelease pool 與 線程

每一個線程(包括主線程)都有一個NSAutoreleasePool棧. 當一個新的池子被創(chuàng)建的時候, push進棧. 當池子被釋放內(nèi)存時, pop出棧. 對象調(diào)用autorelease方法進入棧頂?shù)某刈又? 當線程結(jié)束的時候, 它會自動地銷毀掉所有跟它有關(guān)聯(lián)的池子.


線程中的自動釋放池棧

autorelease pool 與 RunLoop

autorelease pool 與 RunLoop


程序運行 -> 開啟事件循環(huán) -> 發(fā)生觸摸事件 -> 創(chuàng)建自動釋放池 -> 處理觸摸事件 -> 事件對象加入自動釋放池 -> 一次事件循環(huán)結(jié)束, 銷毀自動釋放池.

在開始每一個事件循環(huán)之前系統(tǒng)會在主線程創(chuàng)建一個自動釋放池, 并且在事件循環(huán)結(jié)束的時候把前面創(chuàng)建的釋放池釋放, 回收內(nèi)存

蘋果在主線程 RunLoop 里注冊了兩個 Observer:

第一個 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(準備進入睡眠) 和 Exit(即將退出Loop),

BeforeWaiting(準備進入睡眠)時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;

Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。

NSAutoreleasePool何時釋放??

分兩種情況:

1.手動加NSAutoreleasePool是在大括號結(jié)束釋放

2.沒手動加時在當前的runloop迭代時結(jié)束時候釋放

小實驗:

@property(nonatomic, weak)NSString *string_weak;

- (void)viewDidLoad {

? ? [super viewDidLoad];

? ? // 場景 1

? ? NSString *string = [NSString stringWithFormat:@"1234567890"];

? ? self.string_weak= string;

?? ? NSLog(@"string: %@",self.string_weak);

? ? //場景 2

? ? //? ? ? ? @autoreleasepool {

? ? //? ? ? ? ? ? NSString *string = [NSString stringWithFormat:@"1234567890"];

? ? //? ? ? ? _string_weak = string;

? ? //? ? ? ? }

? ? //? ? NSLog(@"string: %@",_string_weak);

? ? // 場景 3

? ? //? ? ? ? NSString *string = nil;

? ? //? ? ? ? @autoreleasepool {

? ? //? ? ? ? ? ? string = [NSString stringWithFormat:@”1234567890”];

? ? //? ? ? ? ? ? _string_weak = string;

? ? //? ? ? ? }

?// NSLog(@"string: %@",self.string_weak);

}

- (void)viewWillAppear:(BOOL)animated {

? ? [superviewWillAppear:animated];

? ? NSLog(@"string: %@", self.string_weak);

}

- (void)viewDidAppear:(BOOL)animated {

? ? [superviewDidAppear:animated];

? ? NSLog(@"string: %@", self.string_weak);

}

輸出:

場景一:

string: 1234567890

string: 1234567890

string: (null)

?分析:使用stringWithFormat,string對象加入當前runloop的自動釋放池,此時string對象的引用為strong和autoreleasepool;當作用域結(jié)束后strong引用用消失但autoreleasepool的引用還在,所以在viewWillAppear方法里面string: 1234567890;viewDidAppear方法時autoreleasepool銷毀,autoreleasepool的引用消失,此時string銷毀為null

場景二:

?string: (null)

string: (null)

string: (null)

分析:string在autoreleasepool的作用域結(jié)束后釋放

場景三:

string: 1234567890

string: (null)

string: (null)

分析:結(jié)合上面兩個場景可以分析出


autoreleasepool原理:

//將oc代碼翻譯成c++代碼

int main(int argc,char* argv[]) {

? ? @autoreleasepool{

? ? }

}

翻譯為:

int main(int argc,char* argv[]){

????????{

????????????????__AtAutoreleasePool __autoreleasepool;?

? ????????? }

????} //也就是生成了一個__AtAutoreleasePool類型的__autoreleasepool實例

?__AtAutoreleasePool是一個結(jié)構(gòu)體:

struct __AtAutoreleasePool {

? __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush();}

? ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}

? void* atautoreleasepoolobj;

}; //__autoreleasepool中含有一個atautoreleasepoolobj指針,并且初始化和銷毀時分別調(diào)用了objc_autoreleasePoolPush()和Pop方法


已經(jīng)有人在蘋果的官方源碼里找到了關(guān)于AutoreleasePool的底層實現(xiàn)NSObject.mm里,源碼地址:https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html

void*objc_autoreleasePoolPush(void){

if(UseGC) returnNULL;//如果使用垃圾回收機制

return AutoreleasePoolPage::push();

}

void objc_autoreleasePoolPop(void*ctxt){

????if(UseGC)return;// fixme rdar://9167170

????if(!ctxt)return;?

?????AutoreleasePoolPage::pop(ctxt);

}

//也就是池子的創(chuàng)建和銷毀根本上是調(diào)用了AutoreleasePoolPage的push和pop方法

AutoreleasePoolPage結(jié)構(gòu)

? ??????AutoreleasePoolPage中存放的是加入池子的oc對象

????????magic 用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整;

????????next 棧頂,初始時在哨兵對象的下一個位置,每插入一個元素next都會是這個元素的下一個位置

????????begain 代表AutoreleasePoolPage的開始位置

????????end 代表AutoreleasePoolPage的結(jié)束位置,每個AutoreleasePoolPage會開辟4096字節(jié)內(nèi)存

????????thread 指向當前線程;

????????parent 指向父結(jié)點,第一個結(jié)點的 parent 值為 nil ;

????????child 指向子結(jié)點,最后一個結(jié)點的 child 值為 nil ;

????????depth 代表深度,從 0 開始,往后遞增 1;

????????hiwat 代表 high water mark 。

一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象,通過parent和child指針連接成鏈表,后來的autorelease對象在新的page加入。


某個線程的 autoreleasepool 堆棧的內(nèi)存結(jié)構(gòu)圖

上圖堆棧由三個 AutoreleasePoolPage 結(jié)點組成,第一個 AutoreleasePoolPage 結(jié)點為 coldPage() ,最后一個 AutoreleasePoolPage 結(jié)點為 hotPage(),總共有兩個 POOL_SENTINEL (哨兵)。

首先我來介紹幾個概念

1.POOL_SENTINEL(哨兵對象):

在每個自動釋放池初始化調(diào)用 objc_autoreleasePoolPush 的時候,都會把一個 POOL_SENTINEL push 到自動釋放池的棧頂,并且返回這個 POOL_SENTINEL 哨兵對象。

void * atautoreleasepoolobj = objc_autoreleasePoolPush();

?objc_autoreleasePoolPop(atautoreleasepoolobj);

上面的 atautoreleasepoolobj 就是一個 POOL_SENTINEL

2.push 操作

void *objc_autoreleasePoolPush(void){

? ? if (UseGC) return nil;

? ? return AutoreleasePoolPage::push();

}

static inline void *push(){

? ? id *dest = autoreleaseFast(POOL_SENTINEL);

? ? assert(*dest == POOL_SENTINEL);

? ? return dest;

}

一個 push 操作其實就是創(chuàng)建一個新的 autoreleasepool ,對應(yīng) AutoreleasePoolPage 的具體實現(xiàn)就是往 AutoreleasePoolPage 中的 next 位置插入一個 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的內(nèi)存地址,在執(zhí)行 pop 操作的時候作為函數(shù)的入?yún)ⅰ?/p>

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);

? ? }

}

autoreleaseFast 函數(shù)(將對象插入到池子)在執(zhí)行一個具體的插入操作時,分別對三種情況進行了不同的處理:

.當前 page 存在且沒有滿時,直接將對象添加到當前 page 中,即 next 指向的位置;

.當前 page 存在且已滿時,創(chuàng)建一個新的 page ,并將對象添加到新創(chuàng)建的 page 中;

.當前 page 不存在時,即還沒有 page 時,創(chuàng)建第一個 page ,并將對象添加到新創(chuàng)建的 page 中。

3.[obj?autorelease] 操作

- (id)autorelease {

? ? return ((id)self)->rootAutorelease();

}

id objc_object::rootAutorelease2(){

? ? assert(!isTaggedPointer());

? ? return AutoreleasePoolPage::autorelease((id)this);

}

AutoreleasePoolPage 的 autorelease 函數(shù)的實現(xiàn)對我們來說就比較容量理解了,它跟pool push 操作的實現(xiàn)非常相似。只不過pool push 操作插入的是一個 POOL_SENTINEL ,而 autorelease 操作插入的是一個具體的 autoreleased 對象。下面是AutoreleasePoolPage::autorelease代碼:

static inline id autorelease(id obj){

? ? assert(obj);

? ? assert(!obj->isTaggedPointer());

? ? id *dest __unused = autoreleaseFast(obj);

? ? assert(!dest? ||? *dest == obj);

? ? return obj;

}

4.pop操作

同理,前面提到的 objc_autoreleasePoolPop(void *) 函數(shù)本質(zhì)上也是調(diào)用的 AutoreleasePoolPage 的 pop 函數(shù)

void objc_autoreleasePoolPop(void *ctxt){

? ? if (UseGC) return;

? ? // fixme rdar://9167170

? ? if (!ctxt) return;

? ? AutoreleasePoolPage::pop(ctxt);

}

pop 函數(shù)的入?yún)⒕褪?push 函數(shù)的返回值,也就是 POOL_SENTINEL 的內(nèi)存地址,就是pool token 。當執(zhí)行 pop 操作時,內(nèi)存地址在 pool token 之后的所有 autoreleased 對象都會被 release 。直到 pool token 所在 page 的 next 指向 pool token 為止。

總結(jié):每當進行一次objc_autoreleasePoolPush調(diào)用時,runtime向當前的AutoreleasePoolPage中add進一個哨兵對象,當需要release時,objc_autoreleasePoolPop(哨兵對象)作為入?yún)ⅲ鶕?jù)傳入的哨兵對象地址找到哨兵對象所處的page。在當前page中,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息,并向回移動next指針到正確位置

代碼實戰(zhàn):

對象的autorelease方法會將當前對象加入棧頂?shù)腘SAutoreleasePool;NSAutoreleasePool是棧式管理,先入棧的后出棧,對NSAutoreleasePool對象發(fā)送release或者drain會銷毀棧頂?shù)腘SAutoreleasePool對象一直到銷毀當前的NSAutoreleasePool對象

子線程的autoreleasepool?

每個線程創(chuàng)建的時候就會創(chuàng)建一個autorelease pool,并且在線程退出的時候,清空autorelease pool。所以子線程的autorelease對象,要么在子線程中設(shè)置runloop清除,要么子線程結(jié)束時清除

TestObject:

+ (instancetype) instanceWithNumber:(NSInteger)number{

? ? //不加__autoreleasing 會因為arc返回值優(yōu)化而不是一個autorelease對象,可以去掉試試

? ? __autoreleasing TestObject*object = [[TestObject alloc]init];

? ? return object;

}

-(void)dealloc{

? ? NSLog(@"Test Object dealloc");

}

ViewController:

? __weak id _testObject;

? NSThread*_testThread;

- (void)viewDidLoad {

? ? [super viewDidLoad];

? ? _testThread = [[NSThread alloc] initWithTarget:self

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? selector:@selector(testBuild)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? object:nil];


? ? [_testThread start];

? ? [self performSelector:@selector(testAlert) onThread:_testThread withObject:nil waitUntilDone:NO];

}

- (void)testBuild{

? ? TestObject*testObject = [TestObjectinstanceWithNumber:10];

? ? _testObject= testObject;

}

- (void)testAlert{

? ? NSLog(@"test alert");

? ? NSLog(@"test alert");

? ? NSLog(@"test alert");

}


如何詳細的查看堆棧信息?

在 Debug Navigator 下面有個搜框, 旁邊有三個按鈕, 把第一個按鈕點擊一下, 取消選中就可以了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容