[編寫高質量iOS程式碼的52個有效方法](七)記憶體管理(上)
[編寫高質量iOS程式碼的52個有效方法](七)記憶體管理(上)
參考書籍:《Effective Objective-C 2.0》 【英】 Matt Galloway
先睹為快
29.理解引用計數
30.以ARC簡化引用計數
31.在dealloc方法中只釋放引用並解除監聽
32.編寫異常安全程式碼時留意記憶體管理問題
目錄
第29條:理解引用計數
Objective-C語言使用引用計數來管理記憶體,也就是說每個物件都有個可以遞增或遞減的計數器。如果想使某個物件繼續存活,那就遞增其引用計數;用完之後,就遞減其計數。計數變為0,就表示無人關注此物件了,於是,就可以把它銷燬。
在引用計數架構下,物件有個計數器,用以表示當前有多少個事物想令此物件繼續存活下去。這在Objective-C中叫做保留計數,也叫引用計數。NSObject協議宣告瞭下面3個方法用於操作計數器,以遞增或遞減其值(ARC中不可用):
retain // 遞增保留計數
release // 遞減保留計數
autorelease // 待稍後清理自動釋放池時再遞減保留計數
物件建立出來時,其保留計數至少為1.若想令其繼續存活,則呼叫retain方法。要是某部分程式碼不再使用此物件,那就呼叫release或autorelease方法。最終保留計數歸0時,物件就回收了。
為了避免在不經意間使用了無效物件,一般呼叫完release之後都會清空指標。這就能保證不會出現可能指向無效物件的指標,這種指標通常被稱為懸掛指標。
NSMutableArray *array = [[NSMutableArray alloc] init];
// number建立,保留計數為1
NSNumber *number = [[NSNumber alloc] initWithInt:1337];
// 陣列引用了number,number保留計數為2
[array addObject:number];
// 釋放number,保留計數為1
[number release];
// 呼叫release後,無法保證number仍然存活(雖然在本例中可以明顯看到number還存活),應當清空指標。
number = nil;
屬性為strong時,屬性的setter方法是先保留新值再釋放舊值,然後更新例項變數,使其指向新值。
- (void)setFoo:(id)foo{
[foo retain];
[_foo release];
_foo = foo;
}
執行順序呢很重要,假如還未保留新值就先把舊值釋放了,而且兩個值又指向同一個物件,那麼先執行的release操作就可能導致系統將此物件永久回收。而後續的retain操作則無法令這個已徹底回收的物件復生,例項變數就成為懸掛指標。
呼叫release會立刻遞減物件的保留計數(可能會令系統回收此物件),然而有時候可以不呼叫它,改為呼叫autorelease,此方法會在稍後遞減計數。(通常是下一次時間迴圈時遞減)
- (NSString*)stringValue{
NSString *str = [[NSString alloc] initWithFormat:@"I am this %@",self];
return str;
}
此時返回的str物件其保留計數比期望值要多1,因為呼叫alloc會令保留計數加1,而又沒有與之對應的釋放操作。也不能在方法內釋放str,否則還沒等方法返回,系統就把物件回收了。這裡應該使用autorelease,它會在稍後釋放物件。
- (NSString*)stringValue{
NSString *str = [[NSString alloc] initWithFormat:@"I am this %@",self];
return [str autorelease];
}
第30條:以ARC簡化引用計數
使用ARC時,引用計數實際上還是在執行。只不過保留與釋放操作現在是由ARC自動新增。因此直接在ARC下呼叫retain、release、autorelease、dealloc這些記憶體管理方法是非法的。
實際上,ARC在呼叫這些方法時,直接呼叫的是其底層C語言版本,如ARC會呼叫與retain等價的底層函式objc_retain,這樣做效能更好,這也是不能重寫retain、release、autorelease的原因。
ARC確立了方法名的硬性規定。若方法名以alloc、new、copy、mutableCopy開頭,其返回物件歸呼叫者所有。其他方法返回的物件並不歸呼叫者所有。
+ (EOCPerson*)newPerson{
EOCPerson *person = [[EOCPerson alloc] init];
return person;
// 方法以new開頭,返回值歸呼叫者所有,釋放由呼叫者負責,ARC不會在這裡自動新增語句
}
+ (EOCPerson*)somePerson{
EOCPerson *person = [[EOCPerson alloc] init];
return person;
// 返回值不歸呼叫者所有,ARC會自動將return person替換為等價return [person autorelease]的語句
}
- (void)doSomething{
EOCPerson *person1 = [EPCPerson newPerson];
EOCPerson *person2 = [EPCPerson somePerson];
// ARC會自動新增等價[person1 release]的語句(person1是由這塊程式碼所有)
}
在應用程式中,可用下列修飾符來改變區域性變數與例項變數的語義:
__strong // 預設語義,保留此值
__unsafe_unreatined // 不保留此值,可能不安全,出現懸掛指標
__weak // 不保留此值,安全,系統回收物件時會清空物件
__autorelease // 把物件按引用傳遞給方法時,使用該修飾符。此值在方法返回時自動釋放。
在手動管理引用計數時,可能會像下面這樣來編寫dealloc方法清空例項變數
- (void)dealloc{
[_foo release];
[_bar release];
[super dealloc];
}
用了ARC之後,就不需要再編寫這樣的dealloc方法了,ARC會自動清理記憶體。不過,如果有非Objective-C對像(如CoreFoundation)中的物件或是由malloc()分配在堆中的記憶體,仍然需要清理。在ARC中不能直接呼叫dealloc,但是可以重寫dealloc方法,ARC會自動執行此方法,並呼叫其中超類的dealloc方法。
- (void)dealloc{
// 釋放非Objective-C物件
CFRelease(_coreFoundationObject);
// 釋放malloc()分配的堆記憶體
free(_heapAllocatedMemoryBlob);
}
第31條:在dealloc方法中只釋放引用並解除監聽
物件在經歷其生命期後,最終會為系統所回收,這時就要執行dealloc方法了。在每個物件的生命週期內,此方法僅執行一次,也就是當保留計數降為0的時候。dealloc方法會由執行期系統呼叫,開發者不能自己呼叫。
ARC會自動釋放所有Objective-C物件,dealloc中需要手動釋放非Objective-C物件,除此之外,還需要把原來配置過的觀測行為都清理掉。如果用NSNotificationCenter給此物件註冊過某種通知,那麼一般應該在這裡登出。不然通知系統可能會把通知傳送給已回收的物件,引起系統崩潰。
- (void)dealloc{
CFRelease(_coreFoundationObject);
// 登出通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
雖說應該於dealloc中釋放引用,但是開銷較大或系統內稀缺資源不在此列。如檔案描述符、套接字、大塊記憶體,不然會導致保留稀缺資源時間過長。通常應該實現另一個方法,但應用程式用完資源後就呼叫此方法清理資源。再在dealloc中進行檢查,防止開發者忘記清理資源。
// 清理資源的方法
- (void)close{
/* 清理資源 */
_closed = YES;
}
- (void)dealloc{
// 如果忘記清理資源,則輸出錯誤日誌並清理資源
if(!_closed){
NSLog(@"ERROR:close was not called before dealloc!");
[self close];
}
}
本例中,在dealloc呼叫了其它方法,不過是為了偵測程式設計錯誤而破例。正常情況下,不要在dealloc中隨便呼叫其他方法。因為物件已經處於正在回收狀態,如果在這裡呼叫的方法又要非同步執行某些任務,等任務結束後這個物件可能已經被徹底摧毀了,導致程式崩潰。
dealloc裡也不要呼叫屬性的存取方法,屬性可能正處於KVO機制的監控之下,屬性的觀察者可能會在屬性值改變時保留或使用這個即將回收的物件,導致錯誤。
第32條:編寫異常安全程式碼時留意記憶體管理問題
Objective-C的錯誤模型表明,異常只應在發生嚴重錯誤後丟擲,不過有時候仍然需要編寫程式碼來捕獲並處理異常。
使用手動計數時:
@try{
EOCSomeClass *object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch(...){
Nslog(@"There was an error!");
}
// 無論是否發生異常,@finally塊中的程式碼都會執行
@finally{
[object release];
}
使用@finally塊可以在發生異常時也能釋放物件。
而ARC環境下,不能呼叫release,無法像手動管理那樣將釋放操作移到@finally塊中。ARC又不會自動處理,這種情況下可以開啟編譯器的-fobjc-arc-exceptions標誌來開啟ARC生成安全處理異常所用的附加程式碼。只是這段程式碼會嚴重影響執行期的效能,即使不丟擲異常。
所以一般來說,只有當應用程式必須因異常情況而終止時才應丟擲異常,這時候應用程式即將終止,是否發生記憶體洩漏已經無關緊要了,因此不用新增安全處理異常所用的附加程式碼了。如果有大量異常捕獲操作時,應考慮重構程式碼,用21條的NSError式錯誤資訊傳遞法來取代異常。
相關文章
- 《Effective JavaScript 編寫高質量JavaScript程式碼的68個有效方法》JavaScript
- iOS 編寫高質量Objective-C程式碼(七)iOSObjectC程式
- 一篇文章拿下《Effective Objective C 2 0編寫高質量iOS與OS X程式碼的52個有效方法》ObjectiOS
- iOS 編寫高質量Objective-C程式碼iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(八)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(五)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(三)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)—— 簡介iOSObjectC程式
- 如何編寫高質量的C#程式碼(一)C#
- 每日10行程式碼52:編寫高質量python程式碼方法4——用輔助函式來取代複雜的表示式行程Python函式
- 《編寫高質量程式碼:改善Java程式的151個建議》筆記Java筆記
- 編寫高質量程式碼的十個祕訣
- 我們應該如何編寫高質量的前端程式碼前端
- 編寫高質量程式碼 改善Python程式的91個建議Python
- 提升團隊效率:高質量軟體設計文件的編寫方法
- 編寫靈活、穩定、高質量的HTML程式碼的規範HTML
- 編寫靈活、穩定、高質量的CSS程式碼的規範CSS
- 🐒編寫高質量程式碼(手撕程式碼)
- iOS 記憶體管理iOS記憶體
- 消除程式碼中的壞味道,編寫高質量程式碼
- 高階記憶體管理程式設計指南-實用的記憶管理記憶體程式設計
- iOS 記憶體管理MRCiOS記憶體
- “理解”iOS記憶體管理iOS記憶體
- iOS 記憶體管理研究iOS記憶體
- 編寫高質量箭頭函式的5個最佳做法函式
- 編寫靈活、穩定、高質量的CSS程式碼的規範(推薦收藏)CSS
- 編寫高質量可維護的程式碼:一目瞭然的註釋
- iOS記憶體管理詳解iOS記憶體
- 讀Flink原始碼談設計:有效管理記憶體之道原始碼記憶體
- 如何編寫高質量的函式 -- 敲山震虎篇函式
- 理解 iOS 和 macOS 的記憶體管理iOSMac記憶體
- 《編寫高質量程式碼--web前端開發修煉之道》筆記-CSSWeb前端筆記CSS