本文記錄Objective-C在內存管理方面的一些注意點。另有一篇轉載的未公開筆記——Objective-C內存管理機制學習筆記【轉】。html
在引用計數中,每個對象負責維護對象全部引用的計數值。編程
對象的初始引用計數值爲1。若是引用計數的值爲0,則對象就會自動dealloc。ide
包含alloc/new/copy/mutableCopy的方法 引用計數+1函數
retain 引用計數+1學習
release 引用計數-1ui
在 Objective-C 有2種管理內存的方法, 1) retain and release or 2) retain and release/autorelease。url
對於每一個 retain,必定要對應一個 release 或一個 autorelease。spa
查看方法:[object retainCount];指針
生成並持有對象 | alloc/new/copy/mutableCopy等方法 |
持有對象 | retain方法 |
釋放對象 | release方法 |
廢棄對象 | deallco方法 |
我的理解,所謂持有對象,能夠簡單理解爲負責該對象的釋放。code
1. obj對象建立後, 計數爲1
2. obj對象加入到array中後,計數+1,爲2
3. obj對象從array中移除後,計數-1,爲1
Object *obj = [[Object alloc] init]; //1 [array addObject:obj]; //2 [array removeObjectAtIndex:0]; //1 /*此時obj的引用計數爲1,內存泄漏*/
addObject和removeObjectAtIndex是一對,由系統管理引用計數。而咱們輸入的Object *obj = [[Object alloc] init];並無一個release與之對應,因此形成obj沒有被正確釋放。
解決方法是在obj對象添加到array後,release它。
Object *obj = [[Object alloc] init]; //1 [array addObject:obj]; //2 [obj release]; //1 [array removeObjectAtIndex:0]; //0 /*此時obj的引用計數爲0,內存不泄漏*/
NSArray* immutableArray = [[NSArray alloc] initWithArray:mutableArray] NSArray* immutableArray = [NSArray arrayWithArray:mutableArray]; NSArray* immutableArray = [mutableArray copy];
1. alloc和copy都會分配內存,須要手動release。因此調用第一個和第三個都須要 [immutableArray release].
2. arrayWithArray也會分配內存,不過系統會來管理這塊內存,不須要手動release。若是想要本身管理,能夠這樣:
NSArray* immutableArray = [[NSArray arrayWithArray:mutableArray] retain];
[immutableArray release];
當類中包含其餘指針,就要在dealloc函數中手動一一release它們,最後記得[super dealloc]。
- (NSString *)f { NSString *result = [[NSString alloc] initWithFormat:@"Hello"]; return result; }
這樣作實際上是會內存泄漏的。alloc方法會建立出來一個string對象,它的retain計數爲1。所以該string對象返回時,retain計數爲1。在其餘對象調用f方法獲得string對象後,它通常會retain該string對象(由於調用者認爲f返回的應該是一個autorelease對象)。這時,string對象的retain計數變成2。而後調用者在再也不須要stirng對象時,他將會調用release(由於他retain了一次,因此會release一次)。這時string對象的retain計數變成1。正如你所想, string對象沒有獲得釋放。
錯誤的解決方法:讓函數返回前使用[result release];
這樣返回的函數對象其實已是空的了。不可行。
正確的解決方法:讓函數返回一個autorelease對象。
- (NSString *)f { NSString *result = [[NSString alloc] initWithFormat:@"Hello"]; return [result autorelease]; }
Autorelease實際上只是把對release的調用延遲了,對於每個Autorelease,系統只是把該Object放入了當前的Autorelease pool中,當該pool被釋放時,該pool中的全部Object會被調用Release。
autorelease和release沒什麼區別,只是引用計數減一的時機不一樣而已,autorelease會在對象的使用真正結束的時候才作引用計數減一。
函數返回的是一個autorelease對象,而接到它的對象通常須要retain,而後有retain就須要咱們手動release。
若是有AutoreleasePool,那麼在內存池管理的範圍內的autorelease都不須要咱們手動釋放。
自動釋放池對象一般以以下模式建立:
[[[Object alloc] init] autorelease];
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init]; A *a = [[A alloc] init]; //引用計數爲1 [pool drain]; //引用計數依然爲1 [a retain]; //引用計數爲2 NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init]; [a autorelease]; //將a添加到pool中,當pool釋放的時候,a也被釋放 [pool drain]; //引用計數爲1 [a release]; //引用計數爲0
- (void)f { for(int i = 0; i < 100000; ++i) { //getData返回一個autorelease對象 NSData *data = [self getData]; } //在這裏100000個數據對象都還有效 }
因此,autorelease可能致使存在大量臨時對象。
解決方法1:在循環中釋放對象
- (void)f { for(int i = 0; i < 100000; ++i) {
NSData *data = [[NSData alloc]init]; /* * set data, use data */
[data release]; } //在這裏100000個數據對象都被成功釋放 }
解決方法2:循環內部建立一個自動釋放池
- (void)f { for(int i = 0; i < 100000; ++i) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init]; //getData返回一個autorelease對象 NSData *data = [self getData]; [pool drain]; } //在這裏100000個數據對象都被成功釋放 }
- (void)setName:(NSString *)newName { name = newName; }
這樣寫有什麼不對的地方呢,當newName在某個地方被release後,該name將失效!
改進後的寫法應該以下,以防其餘人釋放name引用的對象而致使name失效。
- (void)setName:(NSString *)newName { name = newName; [name retain]; }
這樣寫也有問題,當第二次調用setName的時候,原來的name佔的空間並無釋放;並且retain以後何時release這個對象?
改進後的寫法應該以下
- (void)setName:(NSString *)newName { [name release]; //釋放舊值 name = [newName retain]; } - (void)dealloc { [name release]; //對應setName中的retain [super dealloc]; }
但是但是但是,這樣仍是有問題,假如本身傳值給本身的時候會怎樣呢?因此,最正確的應該是
- (void)setName:(NSString *)newName { [newName retain]; //注意,順序必定是先retain再release。 [name release]; name = newName; } - (void)dealloc { [name release]; //對應setName中的retain [super dealloc]; }
注意,順序必定是先retain再release。固然還有其餘寫法,詳見《Cocoa® Programming for Mac® OS X》中的內存管理章節,不過我的比較推崇這種寫法。
最後的問題是,當newName改變的時候,name也會跟着改變,由於這是淺複製。若是想要讓兩者獨立的話,即深複製,應該這樣寫
- (void)setName:(NSString *)newName { if (name != newName) //防止複製自身 { [name release]; name = [[NSString alloc] initWithString:newName]; } } - (void)dealloc { [name release]; [super dealloc]; }
1.
NSNumber *myInt = [NSNumber numberWithInteger:100]; //引用計數爲1
2.
myInt = [myArr objectAtIndex:0]; [myArr removeObjectAtIndex:0];
此時,myInt引用的對象失效。應當修改成:
myInt = [myArr objectAtIndex:0]; [myInt retain]; [myArr removeObjectAtIndex:0];
3.
NSString *s1 = @"s1"; //引用計數爲0xffffffff(不少f就對了) NSString *s2 = [NSString stringWithString:@"s2"]; //引用計數爲0xffffffff NSMutableString *s3 = [NSMutableString stringWithString:@"s3"]; //引用計數爲1
爲何呢,由於s1是常量字符串,s2是使用了常量字符串初始化的不可變字符串對象,都沒有引用計數機制。
《Objective-C Beginner's Guide》
《Cocoa® Programming for Mac® OS X》中的內存管理章節
《Objective-C高級編程》中的自動引用計數部分