以下考慮均基于ARC
顧名思義 autoreleasePool 是一種自動的內存管理機制,它的工作機制很簡單,就是把需要自動管理的對象放到 autoreleasePool 中,待 autoreleasePool 結束后把池子中的對象釋放掉。
首先補充說明一點,在OC中 自生成并持有對象的方式只有 alloc/new/copy/mutableCopy 四種 ,其他方式均為非自生成并持有對象 如下代碼
{
//自生成并持有對象
NSMutableArray *array1 = [[NSMutableArray alloc]init];
//非自生成并持有對象, 因為array2 持有的是通過 +()array 方法返回的對象
NSMutableArray *array2 = [NSMutableArray array];
}
接著autoreleasePool說,正常情況下,在超出作用域時對象會被自動釋放掉,如下代碼
{
NSObject *obj = [[NSObject alloc]init];
//自己生成并持有對象,引用計數 + 1 (retain)
//retainCount = 1
}
//超出作用域 obj 引用計數 -1 (release)
//此時retainCount = 0 所以 obj 銷毀
retain 和 release 成對出現,不需要另加一個自動釋放池來進行管理,一樣能完成內存的自動回收。這不就扯了嗎?本來就好好的東西,干嘛非得加個自動釋放池呢?看下面的一個例子
- (NSObject *)getObj {
NSObject *obj = [[NSObject alloc]init];
//自己生成并持有對象,引用計數 + 1 (retain)
//retainCount = 1
return obj;
//return 導致提前出作用域 引用計數 -1 (release)
//retainCount = 0 obj 被釋放
}
當我們需要調用這個函數賦值時 如下
{
NSObject *obj_1 = [self getObj];
}
問題來了,正如剛才所說,正常情況下 出作用域時對象會被自動釋放掉,于是就造成了 obj_1 在想取得持有對象時 發現對象被釋放掉了,這顯然是不合理的。這就像是你滿心歡喜在天貓買了個冰棒,拿到快遞時發現冰棒竟然化沒了,你說鬧心不鬧心。
雖然道理是這個道理,但在實際工作時并沒有這種情況發生,這是怎么回事呢?這其實就是autoreleasePool的功勞了,編譯器會在return 之前提前把對象retain 并 注冊到自動釋放池 大體過程類似下面的代碼(這里只是用于演示過程)
- (NSObject *)getObj {
NSObject *obj = [[NSObject alloc]init];
//自己生成并持有對象,引用計數 + 1 (retain)
//retainCount = 1
//下面這一步由編譯器自動完成
NSObject *autoreleaseObj = obj;
[autoreleaseObj retain];
//obj 引用計數 + 1
//retainCount = 2
[autoreleaseObj autorelease];
//把autoreleaseObj注冊到自動釋放池
return autoreleaseObj;
//return 導致提前出作用域 引用計數 -1 (release)
//retainCount = 1 obj不會被釋放
//此時obj 被autoreleasePool持有
}
那這個pool在哪里呢?對于oc來說,整個程序都是運行在一個pool中的,可以看一下main函數的實現
int main(int argc, char * argv[]) {
//自動釋放池
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
此時由于return后對象被autoreleasePool持有,因此不會被提前釋放掉,obj_1 自然也就可以拿到并持有該對象引用計數+1。這樣,當出作用域時obj_1被釋放,引用計數-1,當autoreleasePool退出時 對象引用計數 -1 至此被注冊到autoreleasePool的對象的引用計數 = 0 被釋放掉。由此完成了內存的自動管理。
問題是解決了,但會造成內存消耗增加,這就好比天貓的員工站出來說,為了保障你的冰棒半途不會化掉,你需要多加點錢我們給你配一個保溫箱。
為什么會增加內存消耗?由于對象會被注冊到自動釋放池,而且在自動釋放池結束前對象一直被持有,因此當大量的對象被注冊到自動釋放池時就會造成內存激增,看下面一段代碼
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i < 10000; i ++) {
for (int j = 0; j < 100; j ++) {
NSMutableArray *array = [self getArray];
NSLog(@"%@",array);
}
}
}
- (NSMutableArray *)getArray {
NSMutableArray *arr = [[NSMutableArray alloc]init];
[arr addObject:@"hh"];
return arr;
}
事實上,只要是調用非自己生成并持有的對象,該對象就會被注冊到自動釋放池,比如下面的方式
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@"hh", nil];
//由于調用的是非自己生成并持有對象,所以會被注冊到自動釋放池,因此在循環調用時一樣會造成內存激增。需要注意
接著內存暴增說,既然自動釋放池會造成內存暴增,那肯定要找方法來解決,我們從自動釋放池的釋放過程入手來解決這個問題,首先說一下自動釋放池的嵌套。
關于嵌套,這里補充一點,當多層自動釋放池嵌套時,內層自動釋放池會屏蔽掉外層自動釋放池對內層自動釋放池中的對象retain,很繞,說的直白一點,就是內層自動釋放池中的對象只會注冊到內層自動釋放池中。如下
//外層池子
@autoreleasepool {
//內層池子
@autoreleasepool {
NSMutableString *poolStr = [NSMutableString stringWithString:@"hh"];
//非自己生成并持有對象,poolStr被注冊到內層池子
}
//內層池子結束,poolStr釋放
}
結合上面自動釋放池內存激增的原理,既然內層池子釋放時,注冊到內層池子的對象也會被釋放,因此對于內存激增的問題,我們可以采用自動釋放池的嵌套來解決,對于上面的代碼我們稍加修改,如下
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i < 10000; i ++) {
//內層池子
@autoreleasepool {
for (int j = 0; j < 100; j ++) {
NSMutableArray *array = [self getArray];
NSLog(@"%@",array);
}
}
}
}
- (NSMutableArray *)getArray {
NSMutableArray *arr = [[NSMutableArray alloc]init];
[arr addObject:@"hh"];
return arr;
}
此時,由于內層池子中的一百次循環完畢后,內層池子便會結束,因此注冊到內層池子的對象便會被即時釋放,因此內存不會繼續增加。
額外補充:其實當開辟一條新的線程時,同時也會創建一個pool用于新線程的內存管理,但是POSIX由于不在ARC的內存管理范圍之內,因此通過pthread創建的新線程需要自己創建atoreleasePool,這一點可以通過打印autoreleasePool得知(打印方法,自行百度),由于class cluster 和 tagged Pointer優化 會造成部分對象的表現異常,比如NSString 和 NSNumber在內容較少時并不會生成真正的對象,因此也不會被加入到自動釋放池。
關于autoreleasePool就先整理到這吧,有不對的地方還請大神們不吝賜教。