參考博文:
https://finalize.com/2013/01/10/using_autoreleasepool_to_reduce_your_memory_footprint/
我對@autoreleasepool原理的理解。
@autoreleasepool
自動釋放池哲戚,這要從MRC 和ARC說起盒让。
MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting)
我們在ARC環(huán)境下寫的代碼况毅,不用自己手動插入“ retain
怔檩、release
”传泊,ARC會在編譯時(shí)為我們在合適的位置插入,釋放不必要的內(nèi)存,但是我們得理解崇渗, Reference Counting
并不是CG,不是垃圾回收。我們r(jià)elease的時(shí)候宅广,只是Reference Counting
減一葫掉,內(nèi)存不會調(diào)用之時(shí)就被馬上釋放掉,那么問題來了乘碑,這個(gè)@autoreleasepool什么時(shí)候釋放挖息。
@autoreleasepool釋放時(shí)間
對于每一個(gè)Runloop
, 系統(tǒng)會隱式@autoreleasepool
什么時(shí)候釋放創(chuàng)建一個(gè)Autorelease pool
兽肤,這樣所有的release pool會構(gòu)成一個(gè)象CallStack一樣的一個(gè)棧式結(jié)構(gòu)套腹,在每一個(gè)Runloop結(jié)束時(shí),當(dāng)前棧頂?shù)腁utorelease pool會被銷毀资铡,這樣這個(gè)pool里的每個(gè)Object會被release电禀。
思考:下面的num和str何時(shí)會被釋放掉。
for(int i = 0; i <lagerNum; i++) {
NSNumber *num = [NSNumber numberWithInt:i];
NSString *str = [NSString stringWithFormat:@"%d ", i];
[NSString stringWithFormat:@"%@%@", num, str];
}
for循環(huán)里面笤休,這個(gè)runloop是要整個(gè)for循環(huán)走完尖飞,里面放在堆內(nèi)存的零時(shí)數(shù)據(jù),才會被釋放掉店雅,如果這個(gè)for循環(huán)的循環(huán)次數(shù)非常的大政基,那么CPU就會爆炸性增長,如上這個(gè)例子闹啦,如果lagerNum = 80W沮明,那么CPU內(nèi)存將會超過400M,出現(xiàn)內(nèi)存警告或app被kill掉窍奋,這個(gè)時(shí)候荐健,就是我們在ARC模式中使用@ autoreleasepool的最佳時(shí)機(jī)。
根據(jù) Apple的文檔 琳袄,使用場景如下:
1.寫基于命令行的的程序時(shí)江场,就是沒有UI框架,如AppKit等Cocoa框架時(shí)窖逗。
2.寫循環(huán)址否,循環(huán)里面包含了大量臨時(shí)創(chuàng)建的對象。(本文的例子)
3.創(chuàng)建了新的線程碎紊。(非Cocoa程序創(chuàng)建線程時(shí)才需要)
4.長時(shí)間在后臺運(yùn)行的任務(wù)在张。
在iOS中獲取當(dāng)前CPU內(nèi)存的代碼
#import <mach/mach.h>
double getMemoryUsage(void) {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self_, TASK_BASIC_INFO, (task_info_t)&info, &size);
double memoryUsageInMB = kerr == KERN_SUCCESS ? (info.resident_size / 1024.0 / 1024.0) : 0.0;
return memoryUsageInMB;
}
我們加入了@ autoreleasepool以后的代碼
lagerNum = 200000;
for (int i = 0; i < lagerNum; i++) {
@autoreleasepool {
NSNumber *num = [NSNumber numberWithInt:i];
NSString *str = [NSString stringWithFormat:@"%d ", i];
[NSString stringWithFormat:@"%@%@", num, str];
if (i == lagerNum - 5) { // 獲取到快結(jié)束時(shí)候的內(nèi)存
float memory = getMemoryUsage();
NSLog(@" 內(nèi)存 --- %f",memory);
}
}
}
內(nèi)存對比
沒有使用 @autoreleasepool ,循環(huán)快結(jié)束時(shí)候的內(nèi)存 --- * 144.34M *
使用 @autoreleasepool ,循環(huán)快結(jié)束時(shí)候的內(nèi)存 --- * 33.67M*
效果非常明顯
加了@autoreleasepool
每一輪的大部分?jǐn)?shù)據(jù)都被釋放掉。
沒加@autoreleasepool
只有小部分被釋放矮慕,整體的內(nèi)存一直在猛增。
結(jié)論
在for循環(huán)內(nèi)部使用了@autoreleasepool
啄骇,能把每一輪的數(shù)據(jù)都及時(shí)釋放掉痴鳄,能在內(nèi)存里面起到一點(diǎn)點(diǎn)小小的優(yōu)化。但是需要注意的是缸夹,如果for里面痪寻,有array的添加能使得Reference Counting增加的操作螺句,那么釋放時(shí)間就會出問題,會發(fā)現(xiàn)@autoreleasepool失去了作用橡类,比如最后這種情況蛇尚。
NSMutableArray * Arr = [NSMutableArray array];
for (int i = 0; i < count; i++) {
@autoreleasepool {
NSNumber * numTep = [NSNumber numberWithInt:i];
[Arr addObject:numTep];
}
}
所以說,@autoreleasepool
里面并不能優(yōu)化一切循環(huán)顾画,這要取決于for循環(huán)內(nèi)部取劫,尤其注意索引計(jì)數(shù)。
思考
在YYCache里面研侣,我見過@autoreleasepool
寫在for循環(huán)外面的情況谱邪。那么@autoreleasepool在for循環(huán)內(nèi)部和for循環(huán)外部,為什么要這么做庶诡?哪種更好惦银?
@autoreleasepool {
for (int i = 0; i < count; i++) {
[pin setObject:values[i] forKey:keys[i]];
}
}