??前面幾節(jié)主要從對(duì)象在內(nèi)存中的生命周期這個(gè)角度,梳理了一下objc的內(nèi)存管理特性。接下來說幾個(gè)和內(nèi)存管理有密切關(guān)系的語(yǔ)言特性。
??本節(jié)主要看一下objc的異常處理部分。我們都知道objc也提供了try-catch機(jī)制,但是在實(shí)際開發(fā)中卻很少使用到,這是為什么呢?
??為了方便說明問題,先不考慮ARC,假設(shè)在非ARC模式下,有如下代碼:
@try {
ClassTest *t = [ClassTest alloc] init];
// 此函數(shù)可能會(huì)拋出異常
[t doSomethingMayThrowException];
[t release];
}
@catch(NSException *e) {
NSLog(@"Oops, there was an exception");
}
??上面的代碼會(huì)產(chǎn)生什么問題嗎?按照try-catch的邏輯,如果在方法doSomethingMayThrowException執(zhí)行時(shí)真的拋出異常,try塊的代碼執(zhí)行會(huì)終止,程序跳轉(zhuǎn)到catch部分,那么實(shí)例t的release方法就不會(huì)被執(zhí)行,從而造成內(nèi)存泄露。
??當(dāng)然,這個(gè)問題也有解決方式,就是補(bǔ)充finally塊來釋放對(duì)象,保證對(duì)象一定會(huì)被釋放,如下:
ClassTest *t;
@try {
t = [ClassTest alloc] init];
// 此函數(shù)可能會(huì)拋出異常
[t doSomethingMayThrowException];
}
@catch(NSException *e) {
NSLog(@"Oops, there was an exception");
}
@finally {
[t release];
}
??為了在finally塊里面引用t對(duì)象,首先需要將t的定義挪到try塊外面,由于本示例中只有一個(gè)對(duì)象,所以感覺還不是特別麻煩。想象一下在實(shí)際的工程中,所有的對(duì)象都需要這樣實(shí)現(xiàn),是不是感覺還是十分頭疼的。而且,隨著代碼量的增加,很容易就忘記釋放某個(gè)對(duì)象,就會(huì)導(dǎo)致內(nèi)存泄露。若泄露的對(duì)象是文件描述符或數(shù)據(jù)庫(kù)連接之類的稀缺資源,就可能導(dǎo)致比較大的問題。如果try塊里面的代碼又有try-catch嵌套,那問題就更麻煩了。
??上面說的是非ARC環(huán)境,那么在ARC環(huán)境下怎么樣呢?系統(tǒng)會(huì)不會(huì)幫我們搞定了一切,我們只需要放心使用就可以了呢?很遺憾,不是。在ARC環(huán)境下,問題反而更嚴(yán)重了,因?yàn)椴荒茉偈謩?dòng)調(diào)用release了,所以無法在finally塊里面手動(dòng)實(shí)現(xiàn)release,必然會(huì)造成內(nèi)存泄露。
??為什么ARC不自動(dòng)去處理這些內(nèi)存管理問題?主要是從性能角度去考慮。前面的幾個(gè)章節(jié)介紹過,ARC并非是objc的一個(gè)語(yǔ)言特性,而是一個(gè)編譯器福利,即使打開ARC,objc的內(nèi)存管理還是通過引用計(jì)數(shù)去實(shí)現(xiàn),只是編譯器在編譯時(shí),自動(dòng)幫用戶插入了retain、release等調(diào)用代碼。那么對(duì)于try-catch機(jī)制來說,要想自動(dòng)處理好內(nèi)存管理,在try塊開始之前要保存所有塊中的變量,這樣做需要插入大量的樣板代碼,以便跟蹤待清理的對(duì)象,從而在拋出異常時(shí)將其釋放。為了達(dá)到這個(gè)目的,付出的代價(jià)就是運(yùn)行期性能的降低,以及添加進(jìn)來的大量的額外代碼會(huì)明顯增加應(yīng)用程序的大小。因此,默認(rèn)情況下ARC是不會(huì)對(duì)try-catch機(jī)制有特殊處理的。關(guān)于try-catch的實(shí)現(xiàn)比較復(fù)雜,可以參考源代碼https://opensource.apple.com/source/objc4的objc-exception.mm等實(shí)現(xiàn)文件幫助理解。
??雖然默認(rèn)情況下ARC不會(huì)自動(dòng)處理try-ctach代碼塊,但蘋果也為用戶提供了這種能力:就是-fobjc-arc-exceptions選項(xiàng)。打開這個(gè)編譯器標(biāo)志可以在ARC模式下較好的處理try-catch的內(nèi)存管理問題,代價(jià)就是性能的下降以及程序體積的上升,而且這也并不意味著所有的問題都會(huì)被正確的處理。
??也是基于以上這些原因,在objc的中比較少使用到try-catch機(jī)制。通常只有當(dāng)應(yīng)用程序必須因異常狀況而終止時(shí)才使用,這時(shí)由于應(yīng)用程序即將終止,即使發(fā)生內(nèi)存泄露也無關(guān)緊要了。而在其他場(chǎng)景下,可以使用NSError機(jī)制來處理程序錯(cuò)誤。