編寫高質(zhì)量iOS與OSX代碼的52個有效方法-第五章:內(nèi)存管理

在OC這種面向?qū)ο蟮恼Z言中蝙茶,內(nèi)存管事是個重要概念齐婴。要想用一門語言寫出內(nèi)存使用效率高而且又沒有bug的代碼冤灾,就得掌握其內(nèi)存管理模型的種種細(xì)節(jié)。

OC引用給自動引用計數(shù)(Automatic Reference Counting,ARC)之后封孙,幾乎吧所有內(nèi)存管理事宜都交由編譯器來決定迹冤。

29、引用計數(shù)

OC通過引用計數(shù)來管理內(nèi)存虎忌,每個對象都已個可以遞增或遞減的計數(shù)器泡徙。如果想要某個對象繼續(xù)存活,就遞增其引用計數(shù)膜蠢,用完之后堪藐,遞減計數(shù)。計數(shù)變?yōu)?挑围,表示沒人關(guān)注此對象礁竞,可以銷毀。

iOS不支持垃圾收集(garbage collector)杉辙。

引用計數(shù)工作原理

引用計數(shù)架構(gòu)下模捂,對象有個計數(shù)器,用以表示當(dāng)前有多少個事物想令此對象繼續(xù)存活下去蜘矢。在OC中叫引用計數(shù)(retain Count)狂男,或者引用計數(shù)(reference count)。

NSObject聲明三個方法用于操作計數(shù)器:

  • Retain 遞增引用計數(shù)
  • release 遞減引用計數(shù)
  • autorelease 待稍后清理自動釋放池(autorelease pool)時品腹,再遞減引用計數(shù)岖食。

查看引用計數(shù)的方法retainCount,不過不太有用舞吭。

對象創(chuàng)建出來后泡垃,引用計數(shù)至少為1,當(dāng)引用計數(shù)歸零時羡鸥,對象就回收了(deallocated)蔑穴,系統(tǒng)會將其占用的內(nèi)存標(biāo)記為可重用。此時兄春,所有指向該對象的引用都變得無效了澎剥。

應(yīng)用周期在生命期中會創(chuàng)建很多對象锡溯,這些對象互相關(guān)聯(lián)赶舆。相互關(guān)聯(lián)的對象構(gòu)成一張對象圖(object graph)。對象如果持有指向其他對象的強(qiáng)引用(strong reference),那么前者就擁有后者祭饭。對象如果想令其所引用的那些對象繼續(xù)存活芜茵,就可將其保留,用完之后倡蝙,再釋放九串。

ARC 切換 MARC
Project -> Target -> build Settin】-> 輸入“AutoMatic” ->Object-C Automatic Reference Count 改為NO

因過早釋放對象而導(dǎo)致的bug很難調(diào)試,因為對象所占的內(nèi)存在解除分配(deallocated)之后,只是放回可用內(nèi)存池(avaiable pool)猪钮。如果再次使用還沒有覆寫對象內(nèi)存品山,那么該對象依然有效,程序不會崩潰烤低,但是如果該對象所占內(nèi)存被回收肘交,再次使用就會崩潰。

為避免不經(jīng)意間使用無效對象扑馁,一般用完release之后,都會清空指針。保證不會出現(xiàn)可能指向無效對象的指針,這種指針通常稱為懸掛指針(dangling pointer)咙好。可以再釋放對象之后,將對象置為nil

NSNumber *number = [[NSNumber alloc] initWithInt:2];
NSMutableArray *array = [NSMutableArray array];
[array addObject:number];
[number release];
number = nil;

屬性存取方法中的內(nèi)存管理

不光數(shù)組,其他對象也可以保留別的對象贮庞,一般通過訪問屬性來實現(xiàn)卤材。而訪問屬性時尉辑,會用到相關(guān)實例變量的獲取方法及設(shè)置方法堤器。

若屬性為strong關(guān)系辉川,則設(shè)置的屬性值會保留。

- (void)setFirstName:(NSString *)firstName {
    [firstName retain];
    [_firstName release];
    _firstName = firstName;
}

此方法將保留新值并釋放舊值妆距,然后更新實例變量,令其指向新值。順序很重要蓬推,加入還未保留新值就把舊值釋放了,而且這兩個值又指向同一個對象喇肋,那么先執(zhí)行的release操作就可能導(dǎo)致系統(tǒng)將此對象永久回收。而后續(xù)的retain操作則無法領(lǐng)這個已經(jīng)徹底回收的對象復(fù)生低葫,于是實例變量就成了懸掛指針善涨。

自動釋放池

調(diào)用release會立刻遞減對象的引用計數(shù)(而且有可能令系統(tǒng)回收此對象)娶靡,有時候可以不調(diào)用它呻此,改為調(diào)用autorelease,此方法會在稍后遞減計數(shù)撩扒,通常是在下一次事件循環(huán)(event loop)時遞減辆脸,也可能執(zhí)行得更早些。

此特性很有用猾编,尤其是在方法中返回對象時更應(yīng)該用它获茬。這種情況下,我們并不總是想令方法調(diào)用者手動保留其值吊履。

- (NSString *)personInfo  {
// alloc 引用計數(shù)多1
    NSString *string = [[NSString alloc] initWithFormat:@"%@",_firstName];
    return [string autorelease];
}
ZYDPersonModel *person = [[ZYDPersonModel alloc] initWithFirstName:@"Json"];
NSString *string = [person personInfo];
NSLog(@"%@",string);

使用autorelease來釋放對象,string將與稍后自動釋放暇榴,多出來的一次保留操作就會被抵消向楼,無需再執(zhí)行內(nèi)存管理操作。而自動釋放池中的釋放操作要等下一次事件循環(huán)時才會執(zhí)行诈乒,如果不需要保留导饲,這里則不用其他操作胀溺。如果需要持有此對象蝎困,就需要寶就录语,并于稍后釋放。

personInfo = [[person personInfo] retain];

...

[personInfo release];

這里不能在方法personInfo內(nèi)部使用release禾乘,否則還沒等方法返回澎埠,系統(tǒng)就把對象回收了。

保留環(huán)

保留環(huán)(retain cycle)始藕,呈環(huán)狀互相引用多個對象蒲稳。這將導(dǎo)致內(nèi)存泄漏,因為循環(huán)中的對象其引用計數(shù)不會將為0伍派。

在垃圾回收環(huán)境中江耀,這種情況會被認(rèn)定為孤島(island of isolation),垃圾回收機(jī)制會把循環(huán)引用的對象全部回收诉植。

OC通常采用弱引用解決此問題祥国,或者從外界命令循環(huán)中的某個對象不再保留另外一個對象。打破保留環(huán),從而避免內(nèi)存泄漏舌稀。


  • 引用計數(shù)機(jī)制通過可以遞增遞減的計數(shù)器來管理內(nèi)存啊犬。對象創(chuàng)建好后,其引用計數(shù)至少為1壁查。若引用計數(shù)為正觉至,則對象繼續(xù)存活。當(dāng)引用計數(shù)將為0睡腿,對象就被回收语御。
  • 在對象生命周期中,其余兌現(xiàn)剛通過引用來保留或釋放此對象嫉到,保留與釋放操作分別會遞增及遞減引用計數(shù)沃暗。

30月洛、以ARC簡化引用計數(shù)

靜態(tài)分析器

是ARC時何恶,引用計數(shù)實際上還是要執(zhí)行的,只不過保留與釋放操作由ARC自動添加嚼黔。

ARC的功能都是基于核心的內(nèi)存管理語義而構(gòu)建的细层,這套標(biāo)準(zhǔn)語義貫穿整個OC語言。

ARC會自動執(zhí)行retain唬涧、release疫赎、autorelease等操作,所以直接在ARC下調(diào)用內(nèi)存管理方法是非法的:retain碎节、release捧搞、autoreleasedealloc狮荔。直接調(diào)用會報編譯錯誤胎撇。

ARC調(diào)用這些方法,并不通過普通的OC消息派發(fā)機(jī)制殖氏,而是直接調(diào)用其底層的C語言版本晚树。這樣性能更好,因為保留及釋放需要頻繁執(zhí)行雅采,所以直接調(diào)用底層的函數(shù)能節(jié)省很多CPU周期爵憎。

使用ARC時必須遵循的方法命名規(guī)則

將內(nèi)存管理語義在方法名中表示出來是OC的管理,而ARC將之確立為硬性規(guī)定婚瓜。這些規(guī)則簡單地體現(xiàn)在方法名上宝鼓,若方法名以下列詞語開頭,則返回對象過調(diào)用者所有:

  • alloc
  • new
  • copy
  • mutableCopy

歸調(diào)用者所有的意思:調(diào)用上述四種方法的那段代碼要負(fù)責(zé)釋放方法所返回的對象巴刻。也就是說席函,這些對象的引用計數(shù)都是正值,調(diào)用了這四種方法的那段代碼要將其中一次保留操作抵消掉冈涧。

若方法名不以上述四個詞語開頭茂附,則表示返回的對象并不歸調(diào)用者所有正蛙。這種情況下,返回的對象會自動釋放营曼,所以其值在跨越方法調(diào)用邊界后依然有效乒验。要使對象多存活一段時間,必須令調(diào)用者保留它才行蒂阱。

除了自動調(diào)用保留和釋放方法外锻全,它可以執(zhí)行一些手工操作很難甚至無法完成的優(yōu)化。如在編譯期鳄厌,ARC會把能夠互相抵消的retain了嚎、release、autorelease操作約簡伶氢。如果同一對象執(zhí)行多次保留和釋放操作,ARC有時可以成對的移除這兩個操作瘪吏。

ARC也包含運(yùn)行期組件癣防。

變量的內(nèi)存管理語義

ARC也會處理局部變量與實例變量的內(nèi)存管理。默認(rèn)情況下肪虎,每個變量都是指向?qū)ο蟮膹?qiáng)引用劣砍。

應(yīng)用中,可以用修飾符來改變局部變量與實例變量的語義:

  • __strong 默認(rèn)語義扇救,保留此值
  • __unsafe_unretained 不保留此值刑枝,這么做可能不安全,因為等到再次使用變量時迅腔,其對象可能已經(jīng)回收了装畅。
  • __weak 不保留此值,但是變量可以安全使用沧烈,因為如果系統(tǒng)把這個對象回收了掠兄,那么變量也會自動清空
  • __autoreleasing 把對象按引用傳遞(pass by Reference)給方法時,使用這個特殊的修飾符,此值在方法返回時自動釋放蚂夕。

我們經(jīng)常會給局部變量加上修飾符迅诬,用以打破塊(block)所引入的保留環(huán)。塊會自動保留其所捕獲的全部對象婿牍,而如果這其中某個對象又保留了塊本身侈贷,那么就可能導(dǎo)致保留環(huán)〉戎可以用__weak局部變量打破這種保留環(huán)俏蛮。

block 與 __weak

ARC如何清理實例變量

使用ARC后,不需要編寫實例變量dealloc方法上遥,ARC會在dealloc方法中自動插入釋放代碼搏屑。

ARC會借用Objective-C++的一項特性來生成清理例程(cleanup routine)》鄢回收Objective-C++對象時辣恋,待回收的對象會調(diào)用所有C++對象的析構(gòu)函數(shù)(destructor)。編譯器發(fā)現(xiàn)某個對象中含有C++對象解幼,就會生成名為.cxx_destruct的方法抑党。ARC借助此特性包警,在該方法中生成清理內(nèi)存所需的代碼撵摆。

有非Objective-C對象,如CoreFoundation中的對象或是由malloc()分配在堆中的內(nèi)存害晦,如需要清理特铝。但是不需要想原來那樣調(diào)用父類dealloc方法。ARC下壹瘟,dealloc方法可以這樣寫:

- (void)dealloc {
    CFRelease(_coreFoundationObject);
    free(_heapAllocatedMemoryBlob);
}

ARC會自動生成回收對象時所執(zhí)行的代碼鲫剿,所以通常無須再編寫dealloc方法。

覆寫內(nèi)存管理方法

不使用ARC時稻轨,可以覆寫內(nèi)存管理方法灵莲。比如說,在實現(xiàn)單例類的時候殴俱,因為單例不可釋放政冻,所以經(jīng)常覆寫release方法,將其替換為空操作(no-op)线欲。

ARC環(huán)境下明场,不能這么做,會干擾到ARC分析對象生命期的工作李丰。而且開發(fā)者不可調(diào)用及覆寫這些方法苦锨,所以ARC能夠優(yōu)化retainreleaseautorelease操作莺治,使之不經(jīng)過OC的消息派發(fā)機(jī)制。

優(yōu)化后的操作,直接調(diào)用隱藏在運(yùn)行期程序庫中的C函數(shù)。


  • 有ARC后,程序員無需擔(dān)心內(nèi)存管理問題盅视。使用ARC變成,可以省去類中的許多樣板代碼除破。
  • ARC管理對象生命期的辦法基本上是:在合適的地方插入保留及釋放操作。在ARC環(huán)境下,變量的內(nèi)存管理語義可以通過修飾符指明,而原來則需要手工執(zhí)行保留及釋放操作绪商。
  • 有方法所返回的對象,其內(nèi)存管理語義總是通過方法名來體現(xiàn)。ARC將次確定為開發(fā)者必須遵守的規(guī)則。
  • ARC只負(fù)責(zé)管理OC對象的內(nèi)存移迫,尤其要注意:CoreFoundation對象不歸ARC管理捐顷,開發(fā)者必須適時調(diào)用CFRetain/CFRelease叮姑。

31、在dealloc方法中只是放引用并解除監(jiān)聽

對象在經(jīng)歷其生命周期后,最終會為系統(tǒng)所回收者春,這時就要執(zhí)行dealloc方法了忠售。在每個對象的生命期內(nèi)泰佳,此方法僅執(zhí)行一次黔宛,就是當(dāng)引用計數(shù)降為0的時候案淋。具體何時執(zhí)行翔烁,無法保證。

dealloc方法中應(yīng)該做些什么虾攻,主要就是釋放對象所擁有的引用媒至,也就是把所有的OC對象都釋放掉条篷,ARC會通過自動生成的.cxx_destruct方法绽媒,在dealloc中自動添加這些釋放代碼。對象中擁有的其他非OC對象也要釋放锨苏。比如CoreFoundation對象必須手動釋放疙教,因為他們是有純C的API所生成的。

dealloc中還要坐一件事伞租,就是把原來配置過的觀測行為(observation behavior)贞谓。比如用NSNotificationCenter訂閱的通知。一般應(yīng)該在這里注銷葵诈。

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    CFRelease(_coreFoundationObject);   
}

手動管理引用計數(shù)而不用ARC的話裸弦,最后還需要調(diào)用[super dealloc];同時需要將當(dāng)前對象所擁有的全部OC對象逐個釋放源请。

雖說應(yīng)該與dealloc中釋放引用篷角,但是開銷較大或系統(tǒng)內(nèi)稀缺的資源不在此列凝果。像是文件描述符(file descriptor)窗看、套接字(socket)除盏、大塊內(nèi)存等徐紧,都屬于這種資源士飒。不能指望dealloc方法必定會在某個特定的時間調(diào)用刊橘,因為有一些無法預(yù)料的東西可能也持有此函數(shù)暇矫。那么保留這些稀缺資源的時間有些過長主之,通常的做法是,實現(xiàn)另一個方法李根,當(dāng)應(yīng)用程序用完資源對象后槽奕,就調(diào)用此方法,這樣一來房轿,資源對象的生命周期就更為明確粤攒。

系統(tǒng)并不能保證每個創(chuàng)建出來的對象的dealloc都會執(zhí)行,所以在清理這些資源的方法應(yīng)該是其他的清理方法囱持,而非dealloc方法夯接。

應(yīng)用程序終止后,其占用的資源會返回操作系統(tǒng)纷妆,對于一些沒有收到dealloc消息的對象盔几,也會消亡。不調(diào)用dealloc是為了優(yōu)化程序效率掩幢。而這也說明系統(tǒng)未必會在每個對象上調(diào)用其dealloc方法逊拍。

在iOS程序中的application delegate中上鞠,含有一個會與程序終止時調(diào)用的方法。如果一定要清理某些對象芯丧,可以在這個方法中調(diào)用那些對象的清理方法芍阎。
- (void)applicationWillTerminate:(UIApplication *)application
如果對象管理著某些資源,在dealloc中也要調(diào)用清理方法缨恒。

編寫dealloc方法時谴咸,不要在里面隨便調(diào)用其他方法≈坠欤可以在里面判斷是否調(diào)用close方法寿冕,未調(diào)用拋出異常。

調(diào)用dealloc那個線程會執(zhí)行最終的釋放操作(final release)椒袍,令對象引用計數(shù)降為0驼唱,而某些方法必須在特定線程里執(zhí)行,在dealloc中調(diào)用這些方法驹暑,無法保證當(dāng)前線程是那些方法所需的線程玫恳。

dealloc中不要調(diào)用屬性的存取方法。因為有人可能會覆寫這些方法优俘,并于其中做一些無法在回收階段安全執(zhí)行的操作京办。此外,屬性可能處于鍵值觀察狀態(tài)下帆焕,該屬性的觀察者可能會在屬性值改變時保留或使用這個即將回收的對象惭婿,這種做法會令運(yùn)行期系統(tǒng)的狀態(tài)完全失調(diào),從而導(dǎo)致一些莫名其妙的錯誤叶雹。


  • 在dealloc方法里财饥,應(yīng)該做的事情是釋放指向其他對象的引用,并取消原來定于的鍵值觀察或NSNotificationCenter等通知折晦,不要做其它事情钥星。
  • 如果對象持有文件描述等系統(tǒng)資源,那么應(yīng)該編寫一個方法來釋放此種資源满着。這樣的類要和其使用者約定:用完資源后必須調(diào)用close方法谦炒。
  • 執(zhí)行異步任務(wù)的方法不應(yīng)該在dealloc中調(diào)用;只能在正常狀態(tài)下執(zhí)行的那些方法不應(yīng)在dealloc中調(diào)用风喇,因為此時對象已經(jīng)處于正在回收的狀態(tài)了宁改。

32、編寫異常安全代碼時留意內(nèi)存管理問題

當(dāng)前運(yùn)行期系統(tǒng)中响驴,C++與OC的異惩盖遥互相兼容。

OC錯誤模型表示豁鲤,異常只應(yīng)發(fā)生在嚴(yán)重錯誤后拋出秽誊。雖說如此,有時仍需要編寫代碼來捕獲并處理異常琳骡。

MARC 模式下:

ZYDPersonModel *oPerson = [[ZYDPersonModel alloc] initWithFirstName:@"Json"];
    
@try {
    // 執(zhí)行可能會有異常的方法
    [oPerson personInfo];
} @catch (NSException *exception) {
    NSLog(@"Something wrong.");
} @finally {
    // 不管是異常锅论,還是正常處理,都要將對象釋放
    [oPerson release];
}

如果所有對象都要如此釋放楣号,就非常乏味最易。而且假如@try塊中的邏輯更為復(fù)雜,含有多條語句炫狱,那么很容易就會因忘記某個對象導(dǎo)致內(nèi)存泄露藻懒。若泄露的對象是文件描述或數(shù)據(jù)庫鏈接等稀缺資源,可能引發(fā)大問題视译,這將導(dǎo)致應(yīng)用程序把所有系統(tǒng)資源都抓在自己手里而不及時釋放嬉荆。

ARC模式下

ZYDPersonModel *personThree = [[ZYDPersonModel alloc] initWithFristName:@"Wellienm" lastName:@"Jone"];
    
@try {
    [personThree performDaysWork];
} @catch (NSException *exception) {
    NSLog(@"Something wrong!");
} @finally {
    //ARC 下不能使用release,問題更大
}
    

這種情況ARC不會自動處理酷含,因為這樣做需要加入大量樣板代碼鄙早,以便跟蹤待清理的對象,從而在拋出異常時將其釋放椅亚∠薹可是,這段代碼會嚴(yán)重影響運(yùn)行期的性能呀舔,即便不拋異常時也是如此弥虐。而且添加進(jìn)來的代碼還會明顯增加應(yīng)用程序的大小。

35-1.png

雖說默認(rèn)狀態(tài)下未開啟媚赖,ARC依舊能生成這種安全處理異常所用的附加代買霜瘪。可以添加-fobjc-arc-exceptions這個編譯器標(biāo)志來開啟此功能省古。默認(rèn)不開啟的原因是:OC代碼中粥庄,只有當(dāng)應(yīng)用程序必須因異常狀況而終止時才應(yīng)拋出異常,因此豺妓,如果應(yīng)用程序即將終止惜互,是否還會發(fā)生內(nèi)存泄露就已經(jīng)無關(guān)緊要了。在應(yīng)用程序必須必須立即終止的情況下琳拭,還去添加安全處理異常所用的附加代碼沒有意義训堆。

有種情況編譯器會自動把-fobjc-arc-exceptions標(biāo)志打開。就是出于Objective-C++模式時白嘁。C++處理異常所用的代碼與ARC實現(xiàn)的附加代碼類似坑鱼,所以令A(yù)RC加入自己的代碼以安全處理異常,性能損失不會太大。

如果手動管理引用計數(shù)鲁沥,而且必須捕獲異常呼股,那么要設(shè)法保證所編代碼能把對象正確清理干凈。

若使用ARC且必須捕獲異常画恰,需要打開編譯器的-fobjc-arc-exceptions標(biāo)志彭谁,但最重要的是:在發(fā)現(xiàn)大量異常需要捕獲操作時,應(yīng)考慮重構(gòu)代碼允扇。


  • 捕獲異常時缠局,一定要注意將try塊內(nèi)所創(chuàng)立的對象清理干凈
  • 在默認(rèn)情況下,ARC不生成安全處理異常所需要的清理代碼考润。開啟編譯器標(biāo)識后狭园,可生成這種代碼,不過會導(dǎo)致應(yīng)程序變大糊治,而且降低運(yùn)行效率唱矛。

33、以弱引用避免保留環(huán)

保留環(huán)俊戳,就是幾個對象都已某種方式互相引用揖赴,形成環(huán),即使最有沒有別的東西會引用環(huán)內(nèi)的對象抑胎,但因?qū)ο笾g尚有引用燥滑,這些引用使他們能繼續(xù)存活下去,而不會為系統(tǒng)回收阿逃。

保留環(huán)铭拧,會導(dǎo)致內(nèi)存泄露。

避免保留環(huán)的最佳方式就是弱引用恃锉。這用引用經(jīng)常用來表示費(fèi)擁有關(guān)系(nonowning relationship)搀菩。將屬性聲明為unsafe_unretained即可。

unsafe_unretainedassign特質(zhì)等價破托,assign通常只用于整體類型肪跋。unsafe_unretained則用于對象類型。這個詞本身就表明所修飾的屬可能無法安全使用土砂,不歸此實例所擁有州既。

OC中還要一項與ARC相伴的運(yùn)行期特性,可以安全使用弱引用:weak萝映。它與unsafe_unretained作用相同吴叶,但是只要系統(tǒng)把屬性回收,屬性值就會自動設(shè)為nil序臂。

當(dāng)實例的引用移除后蚌卤,unsafe_unretained屬性仍指向那個已經(jīng)回收的實例,而weak屬性則指向nil

使用weak而非unsafe_unretained引用可以令代碼更安全逊彭。

如果在已經(jīng)在所至對象已經(jīng)徹底銷毀后還繼續(xù)使用弱引用咸灿,依然是個bug。

一般來說诫龙,如果不擁有某對象析显,就不要保留它鲫咽。這條規(guī)則對collection例外签赃,collection雖然并不直接擁有其內(nèi)容,但是它要代表自己所屬的那個對象來保留這些元素分尸。

有時锦聊,對象中的引用會指向另一個并不歸于自己所擁有的對象,比如Delegate模式就是這樣箩绍。


  • 將某些引用設(shè)為weak孔庭,可避免出現(xiàn)保留環(huán),循環(huán)引用
  • weak引用可以自動清空材蛛,也可以不自動清空圆到。自動清空是隨著ARC而引入的新特性,有運(yùn)行期系統(tǒng)來實現(xiàn)卑吭。在具備自動清空功能的弱引用上芽淡,可以隨意讀取其數(shù)據(jù),因為這種引用不會指向已經(jīng)回收過的對象豆赏。

34挣菲、以自動釋放池塊降低內(nèi)存峰值

釋放對象有兩種方式:一種是調(diào)用release方法,使其引用計數(shù)立即遞減掷邦;二是調(diào)用autorelease方法白胀,將其加入自動釋放池中。自動釋放池用于存放那些需要在稍后某個時刻釋放的對象抚岗。清空(drain)自動釋放池時或杠,系統(tǒng)會向其中的對象發(fā)送release消息。

@autoreleasepool {
    //....
}

內(nèi)存峰值(high-memory waterline)是指應(yīng)用程序在某個特定時間段內(nèi)最大內(nèi)存用量(hightest memeory footprint)宣蔚。

一般情況下無需擔(dān)心自動釋放池的創(chuàng)建問題向抢。iOS的CocoaTouch系統(tǒng)會自動創(chuàng)建一些線程,比如說主線程或是大中樞派發(fā)(Grand Central Dispatch,GCD)機(jī)制中的線程件已,這些線程默認(rèn)都有自動釋放池笋额,每次執(zhí)行時間循環(huán)時,都會將其清空篷扩。 因此一般不需要自己創(chuàng)建自從釋放池兄猩,通常只要一個地方,就是在Main函數(shù)里,用自動釋放吃包裹應(yīng)用程序入口點(main application entry point)枢冤。

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

從技術(shù)角度鸠姨,不是非得有個自動釋放池塊才行,因為塊的結(jié)尾恰好就是應(yīng)用程序的終止處淹真,此時操作系統(tǒng)會把程序所占的內(nèi)存都釋放掉讶迁。話雖如此,但是如果不寫這個塊的話核蘸,那么由UIApplicationMain函數(shù)所自動釋放的那些對象巍糯,就沒有自動釋放池可以容納了,于是系統(tǒng)會發(fā)出警告信息來說明這一情況客扎。所以說祟峦,這個池可以理解成最外圍捕捉全部自動釋放對象所用的池。

自動釋放池可以嵌套徙鱼,嵌套的好處是可以借此控制應(yīng)用程序的內(nèi)存峰值宅楞,使其不致過高。

NSMutableArray *people = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; i ++) {
    
    @autoreleasepool {
        ZYDPersonModel *model = [[ZYDPersonModel alloc] initWithFristName:@"Json" lastName:[NSString stringWithFormat:@"%d",i]];
        [people addObject:model];
    }
}

有時如果會創(chuàng)建很多不必要的本來應(yīng)該提早回收的臨時對象袱吆,增加一個自動釋放池可以解決此問題厌衙。把循環(huán)內(nèi)的代碼包裹在自動釋放池塊中,那么循環(huán)中的自動釋放對象就會放在這個池绞绒,而不是線程的主池里面婶希。加上這個自動釋放池之后,應(yīng)用程序在執(zhí)行循環(huán)時的內(nèi)存峰值就會降低处铛。

自動釋放池機(jī)制就像棧一樣饲趋,系統(tǒng)創(chuàng)建好自動釋放池后,就將其推入棧中撤蟆,而清空自動釋放池奕塑,則相當(dāng)于將其從棧中彈出。在對象上執(zhí)行自動釋放操作家肯,就等于將其放入棧頂?shù)哪莻€池里龄砰。

是否用池來右滑效率,完全取決于具體應(yīng)用程序讨衣。首先監(jiān)控內(nèi)存永亮换棚,判斷其中有沒有需要解決的問題,如果沒完成這一步反镇,就不著急優(yōu)化固蚤。盡管自動釋放池塊的開銷不大,但畢竟還是有的歹茶,盡量不要建立額外的自動釋放池夕玩。


  • 自動釋放池排布在棧中你弦,對象收到autorelease消息后,系統(tǒng)將其放入最頂端的池里燎孟。
  • 合理運(yùn)用自動釋放池禽作,可降低應(yīng)用程序的內(nèi)存峰值。
  • @autoreleasepool能創(chuàng)建出更為輕便的自動釋放池揩页。

35旷偿、用僵尸對象調(diào)試內(nèi)存管理問題

問題:向已經(jīng)回收的對象發(fā)送消息是不安全的。這么做有時可以爆侣,有時不行萍程。取決于對象所占內(nèi)存是否被其他內(nèi)容所覆寫,但是這個又無法確認(rèn)累提。因此程序只是偶爾崩潰尘喝。

僵尸對象(Zombie Object),Cocoa提供的功能斋陪。啟動這項調(diào)試功能之后,運(yùn)行期系統(tǒng)會把所有已經(jīng)回收的實例轉(zhuǎn)化成僵尸對象置吓,而不會真正回收他們无虚。這種對象所在的核心內(nèi)存無法重用個,因此不可能遭到覆寫衍锚。僵尸對象收到消息后友题,會拋出異常,其中準(zhǔn)確說明了發(fā)送過來的消息戴质,并描述回收之前的那個對象度宦。

僵尸對象是調(diào)試內(nèi)存管理問題的最佳方式。

打開僵尸對象調(diào)試:

35-1.png

給僵尸對象發(fā)送消息后告匠,控制臺會打印消息戈抄,應(yīng)用程序則會終止。

ZYDPersonModel *person = [[ZYDPersonModel alloc] initWithFirstName:@"Json"];
[person release];
//釋放之后再發(fā)送消息
NSLog(@"%@",[person personInfo]);

** -[ZYDPersonModel release]: message sent to deallocated instance 0x608000014e90

通過object_getClass()方式獲取類名后专,一個對象在釋放之前划鸽,以及釋放轉(zhuǎn)變成僵尸對象之后,類型發(fā)生變化戚哎。

// 原數(shù)據(jù)類型
=== ZYDPersonMode
// 成為僵尸對象之后
=== _NSZombie_ZYDPersonMode

_NSZombie_ZYDPersonMode是在運(yùn)行期生成的裸诽,當(dāng)首次碰到ZYDPersonMode類的對象要變成僵尸對象時,就會創(chuàng)建這么一個類型凳,創(chuàng)建過程中用到了運(yùn)行期程序塊里的函數(shù)丈冬,它們的功能強(qiáng)大,可以操作類列表(class list)甘畅。

崩潰日志中埂蕊,如果出現(xiàn)類似字段实夹,就說明是僵尸對象的問題。

僵尸類(zombie clas)是從名為_NSZombie_的模板類里復(fù)制出來的粒梦。這些僵尸類沒有多少事情可做亮航,只是充當(dāng)一個標(biāo)記。

運(yùn)行期系統(tǒng)如果發(fā)現(xiàn)NSZombieEnabled環(huán)境變量已設(shè)置匀们,那么久吧dealloc方法調(diào)配成一個會執(zhí)行特定方法的版本缴淋,執(zhí)行到程序末尾,對象所述的類就變成_NSZombie_OriginalClass了泄朴,其中OriginalClass指的是原類名重抖。

對于僵尸對象而言,只是個調(diào)試手段祖灰,制作正式發(fā)行的應(yīng)用程序時不會把這項功能打開钟沛。

僵尸類的作用會在消息轉(zhuǎn)發(fā)例程中體現(xiàn)出來。_NSZombie_類(以及所用從該類拷貝出來的類)并未實現(xiàn)任何方法局扶。此類沒有超類恨统,和NSObject一樣是根類,該類只有一個實例變量三妈,叫做isa畜埋,所有OC的根類必須有此變量。由于這個輕量級的類沒有實現(xiàn)任何方法畴蒲,所以發(fā)給它的全部消息都要經(jīng)過完整的消息轉(zhuǎn)發(fā)機(jī)制悠鞍。

在完整的消息轉(zhuǎn)發(fā)機(jī)制中,__forwarding__是核心模燥,調(diào)試程序時咖祭,可能在棧回溯消息里看過這個類蔫骂。它首先要做的就包括檢查接收消息的對象所屬的類名么翰。若類名前綴是_NSZombie_,則表示消息接收者是僵尸對象纠吴,需要特殊處理硬鞍。


  • 系統(tǒng)在回收對象時,可以不將其真的回收戴已,而是把他轉(zhuǎn)化為僵尸對象固该。通過環(huán)境變量NSZombieEnabled可開啟此功能。
  • 系統(tǒng)會修改對象的isa指針糖儡,令其指向特殊的僵尸類伐坏,從而使該對象變?yōu)榻┦瑢ο蟆=┦惸芟鄳?yīng)所有的選擇子握联,相應(yīng)方式為:打印這一條包含信息內(nèi)容及其接收者的消息桦沉,然后終止應(yīng)用程序每瞒。

36、不要使用retainCount

OC通過引用計數(shù)來管理內(nèi)存纯露。NSObject協(xié)議定義了方法由于查詢對象當(dāng)前的引用計數(shù):

- (NSUInteger)retainCount;

這個方法在ARC中被廢棄了剿骨,即使在MARC中還是有一些問題:

  • 它返回的引用計數(shù)只是某個時間點上的值,該方法并未考慮到系統(tǒng)會稍后把自動釋放池清空埠褪,因而不會將后續(xù)的釋放操作從返回值里減去浓利,這樣的話,這個值就未必能夠真實反映實際的引用計數(shù)钞速。
  • 如果通過不停地釋放操作降低引用計數(shù)贷掖,直至對象被系統(tǒng)回收。加入此對象也在自動釋放池里,那么稍后系統(tǒng)清空池子的時候還要把它釋放一次,將導(dǎo)致崩潰间驮。
  • retainCount可能永遠(yuǎn)不返回0,以為又是系統(tǒng)會優(yōu)化對象的釋放行為牙甫,在引用計數(shù)還是1的時候就把它回收了。只有在系統(tǒng)不打算這么優(yōu)化時狭郑,計數(shù)值才會遞減至0.
NSString *string1 = @"string1";
NSString *string2 = [[NSString alloc] initWithFormat:@"string2"];
NSNumber *numberI = @2;
NSNumber *numberF = @3.14f;
NSLog(@"retainCount : %lu",[string1 retainCount]);
// retainCount : 18446744073709551615
NSLog(@"retainCount : %lu",[string2 retainCount]);
// retainCount : 18446744073709551615
NSLog(@"retainCount : %lu",[numberI retainCount]);
// retainCount : 9223372036854775807
NSLog(@"retainCount : %lu",[numberF retainCount]);
// retainCount : 1

第一腹暖、二個對象的引用計數(shù)(2^64 - 1),第三個引用計數(shù)(2^63 - 1)翰萨。三者都是單例對象(singleton object),所以引用計數(shù)都很大糕殉。系統(tǒng)會盡可能把NSString實現(xiàn)成為單例對象亩鬼。

如果字符串像這里這樣,是個編譯器常量(compile-time constant)阿蝶,那么就可以這樣來實現(xiàn)了雳锋。這種情況下,編譯器會把NSString對象所表示的數(shù)據(jù)放到應(yīng)用程序的二進(jìn)制文件中羡洁,這樣玷过,運(yùn)行程序時就可以直接用了,無需再創(chuàng)建NSString對象筑煮。

NSNumber也雷系辛蚊,它使用了標(biāo)簽指針(tagged pointer)的概念來標(biāo)注特定類型的數(shù)值。這種做法不使用NSNumber對象真仲,而是把與數(shù)值有關(guān)的全部消息都放在指針里面袋马。運(yùn)行期系統(tǒng)會在消息派發(fā)期間檢測到這種標(biāo)簽指針,并對它進(jìn)行相應(yīng)操作秸应,使其行為看上去和真正的NSNumber對象一樣虑凛。這種優(yōu)化只在某些場合使用碑宴,這里的浮點數(shù)就沒有優(yōu)化,其引用計數(shù)就是1桑谍。

對于單例對象延柠,它的引用計數(shù)不會變,這種對象的保留及釋放操作都是空操作(no-op)锣披。即便兩個單例對象之間贞间,引用計數(shù)也各不相同。

系統(tǒng)對引用計數(shù)的處理方式一再聲明:不應(yīng)該總是依賴引用計數(shù)的具體值來編碼盈罐。

即便只是測試榜跌,retainCount的值也不是很有用,由于對象可能處于自動釋放池中盅粪,其他程序庫中也有可能自行保留或釋放對象钓葫,這都會擾亂引用計數(shù)的具體取值。

絕對不要用retainCount票顾,尤其是ARC之后已經(jīng)正式將其廢棄础浮,就跟不應(yīng)該用了。


  • 對象的引用計數(shù)看似有用奠骄,實則不然豆同,因為任何給定時間點上的絕對引用計數(shù)(absolute retain count)都無法反映對象生命周期的全貌。
  • ARC含鳞,retainCount方法正式廢止影锈,ARC下調(diào)用會導(dǎo)致編譯器報錯。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝉绷,一起剝皮案震驚了整個濱河市鸭廷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熔吗,老刑警劉巖辆床,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桅狠,居然都是意外死亡讼载,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門中跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咨堤,“玉大人,你說我怎么就攤上這事晒他≈ㄐ停” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵陨仅,是天一觀的道長津滞。 經(jīng)常有香客問我铝侵,道長,這世上最難降的妖魔是什么触徐? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任咪鲜,我火速辦了婚禮,結(jié)果婚禮上撞鹉,老公的妹妹穿的比我還像新娘疟丙。我一直安慰自己,他們只是感情好鸟雏,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布享郊。 她就那樣靜靜地躺著,像睡著了一般孝鹊。 火紅的嫁衣襯著肌膚如雪炊琉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天又活,我揣著相機(jī)與錄音苔咪,去河邊找鬼。 笑死柳骄,一個胖子當(dāng)著我的面吹牛团赏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耐薯,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼舔清,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了曲初?” 一聲冷哼從身側(cè)響起鸠踪,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎复斥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體械媒,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡目锭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纷捞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢虹。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖主儡,靈堂內(nèi)的尸體忽然破棺而出奖唯,到底是詐尸還是另有隱情,我是刑警寧澤糜值,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布丰捷,位于F島的核電站坯墨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏病往。R本人自食惡果不足惜捣染,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望停巷。 院中可真熱鬧耍攘,春花似錦、人聲如沸畔勤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庆揪。三九已至式曲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嚷硫,已是汗流浹背检访。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留仔掸,地道東北人脆贵。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像起暮,于是被迫代替她去往敵國和親卖氨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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