內存管理關心的是清理(回收)不用的內存,以便內存能夠再次利用。
提供給Objective-C程序員的基本內存管理模型有以下三種:
1)自動垃圾收集。(iOS運行環境并不支持垃圾收集,在這個平臺開發程序時沒有這方面的選項,只能用在Mac OS X 程序上開發。這個機制挺惡心的,用mac電腦的人知道,當內存不足的時候,機器基本上就是卡死了。)
2)手工引用計數和自動釋放池。(這種方式,對程序員的要求很高。自己維護嘛,)
3)自動引用計數(ARC)。
Objective-C為每個對象提供一個內部計數器, 這個計數器跟蹤對象的引用次數。?
所有類都繼承自 NSObject 的對象。
retain和release方法:
當對象被創建或拷貝時候, 引用計數為1 。?
每次保持對象時候, 就發送一條retain消息, 使其引用計數加1 ,?
如果不需要這個對象就是發送一個release消息使其引用計數減1 。
當對象的引用計數為0的時候, 系統就知道不再需要這個對象了, 就會釋放它內存。?
一個對象的創建可以通過alloc分配內存或copy復制,?
這關系到的方法有: alloc, allocWithZone:
copy,?copyWithZone, mutableCopy,mutableCopyWithZone, 這些方法都可以使引用計數為1 ,?
retain會使引用計數加1 ,
release會使引用計數減1 。
重寫dealloc方法
當對象包含其它對象時, 就得在 dealloc中自己釋放它們
~~~
#import???
??
@interface?Song?:?NSObject?{??
????NSString?*title;??
????NSString?*artist;??
????long?int?duration;??
}??
??
//操作方法??
-?(void)start;????
-?(void)stop;?????
-?(void)seek:(long?int)time;??
??
//訪問成員變量方法??
@property?NSString?*title;??
@property?NSString?*artist;??
@property(readwrite)?long?int?duration;??
??
//構造函數??
-(Song*)?initWithTitle:?(NSString?*)?title?andArtist:?(NSString?*)?artist?andDuration:(?long?int?)duration?;??
??
??
@end??
~~~
實現如下:
~~~
#import?"Song.h"??
??
@implementation?Song??
??
@synthesize?title;??
@synthesize?artist;??
@synthesize?duration;??
??
//構造函數??
-(Song*)?initWithTitle:?(NSString?*)?newTitle?andArtist:?(NSString?*)?newArtist?andDuration:(long?int)newDuration?{??
????self?=?[super?init];??
????if?(?self?)?{??
????????self.title?=?newTitle;??
????????self.artist?=?newArtist;??
????????self.duration?=?newDuration;??
????}??
????return?self;??
??????
}??
??
-?(void)start?{??
????//開始播放??
}??
??
-?(void)stop?{??
????//停止播放??
}??
??
-?(void)seek:(long?int)time?{??
????//跳過時間??
}??
??
-(void)?dealloc?{??
????NSLog(@"釋放Song對象...");??
????[title?release];??
????[artist?release];??
????[super?dealloc];??
}??
??
@end??
~~~
調用的main函數
~~~
#import???
#import?"Song.h"??
??
int?main?(int?argc,?const?charchar?*?argv[])?{??
??
????Song?*song1?=?[[Song?alloc]?initWithTitle:@"Big?Big?World"?andArtist:@"奧斯卡.艾美莉亞"?andDuration:180];??
????Song?*song2?=?[[Song?alloc]?initWithTitle:@"It's?ok"?andArtist:@"atomic?kitten"?andDuration:280];??
??????
????//?print?current?counts??
????NSLog(@"song?1?retain?count:?%i",?[song1?retainCount]?);??
????NSLog(@"song?2?retain?count:?%i",?[song2?retainCount]?);??
??????
????//?increment?them??
????[song1?retain];?//?2??
????[song1?retain];?//?3??
????[song2?retain];?//?2??
??????
????//?print?current?counts??
????NSLog(@"song?1?retain?count:?%i",?[song1?retainCount]?);??
????NSLog(@"song?2?retain?count:?%i",?[song2?retainCount]?);??
??????
????//?decrement??
????[song1?release];?//?2??
????[song2?release];?//?1??
??????
????//?print?current?counts??
????NSLog(@"song?1?retain?count:?%i",?[song1?retainCount]?);??
????NSLog(@"song?2?retain?count:?%i",?[song2?retainCount]?);??
??????
????//?release?them?until?they?dealloc?themselves??
????[song1?release];?//?1??
????[song1?release];?//?0??
????[song2?release];?//?0??
??????
????return?0;??
}??
~~~
代碼分析:
在這個main函數中, 聲明了兩個Song對象, 當retain調用增加引用計數, 而release調用減少它。?
調用 [obj?retainCount] 來取得引用計數的 int 值。?
當retainCount到達 0, 兩個對象都會調用 dealloc, 所以可以看到印出了兩個 “釋放Song對象...” 。?
在Song對象釋放的時候, 先要釋放它自己的對象類型成員變量title和artist, 然后再調用[super dealloc] 。
自動釋放池
內存釋放池(Autorelease pool ) 提供了一個對象容器,?
每次對象發送autorelease消息時, 對象的引用計數并不真正變化,?
而是向內存釋放池中添加一條記錄, 記下對象的這種要求,?
直到當內存釋放池發送drain或release消息時,?
當池被銷毀前會通知池中的所有對象, 全部發送release消息真正將引用計數減少。?
~~~
NSAutoreleasePool?*pool?=?[[NSAutoreleasePool?alloc]?init];??
…?…??
[pool?release];//?[pool?drain];??
~~~
這些語句必須要放在下面語句之間, 直到池被釋放,?
一個對象要想納入內存釋放池對象, 必須要發送autorelease。?
~~~
#import???
??
int?main?(int?argc,?const?charchar?*?argv[])?{??
??????
????NSAutoreleasePool?*pool?=?[[NSAutoreleasePool?alloc]?init];??
??????
????NSArray?*weeksNames1?=?[NSArray?arrayWithObjects:???
???????????????????????????@"星期一",@"星期二",@"星期三",@"星期四"??
???????????????????????????,@"星期五",@"星期六",@"星期日",nil];??
??????
????NSArray?*weeksNames2?=?[[NSArray?alloc]?initWithObjects:???
???????????????????????????@"星期一",@"星期二",@"星期三",@"星期四"??
???????????????????????????,@"星期五",@"星期六",@"星期日",nil];??
??????
????//[weeksNames1?release];??
????//[weeksNames1?autorelease];??
????//[weeksNames2?release];??
?????//[weeksNames2?autorelease];??
??????
????NSLog(@"?retain?count:?%i"?,?[weeksNames1?retainCount]?);??
????NSLog(@"?retain?count:?%i"?,?[weeksNames2?retainCount]?);??
??????
???[pool?release];??
????return?0;?????
}??
~~~
NSArray類是Foundation框架提供的不可變數組類,
Foundation框架中對象的創建有兩類方法:?
類方法(+)構造方法和實例方法(-) 構造方法。?
打開NSArray ClassReference文檔, 其中創建對象有關的方法如圖所示。

從NSArray Class Reference文檔可以看出,
以+和類名開頭(去掉NS, 小寫第1 個字母, array),就是類級構造方法,
以-和initWith開頭的就是實例構造方法。?
類級構造方法不能使用 release, 可以不用 autorelease就可以自動納入內存釋放池管理。?
實例構造方法, 如果發出release消息就馬上釋放對象,?
如果發出autorelease消息可以自動納入內存釋放池管理, 不會馬上釋放。?
在iOS開發中由于內存相對少, 因此基本上都采用實例構造方法實例化對象,?
采用發送release消息立刻釋放對象內存。
屬性中的內存管理參數
有的時候我們聲明的對象要跨越對象調用 , 內存管理就會變的更加復雜。?
例如在Song類中有一對成員變量存取的方法,當然可以把它們封裝成屬性, 通過屬性參數來管理內存。?
在Song類中給成員變量設置方法如下:?
~~~
-?(void)?setTitle:(NSString?*)?newTitle?{??
title?=?newTitle;??
}??
-?(void)?setArtist:(NSString?*)?newArtist?{??
artist?=?newArtist;??
}??
~~~
這段代碼事實上有內存泄漏, 當設置一個新的Title時候,title = newTitle只是將指針改變了,?
舊對象并沒有釋放, 所以我們會這樣修改這些方法:?
~~~
-?(void)?setTitle:(NSString?*)?newTitle?{??
[newTitle?retain];??
[title?release];??
title?=?[[NSString?alloc]?initWithString:?newTitle];??
}??
-?(void)?setArtist:(NSString?*)?newArtist?{??
[newArtist?retain];??
[artist?release];??
artist?=?[[NSString?alloc]?initWithString:?newArtist];??
}??
~~~
首先保留新對象, 釋放舊對象, 然后使用實例構造方法實例化新的對象。?
參數newTitle不要在方法中釋放。?
由于基本數據類型(非對象類型) 不需要釋放, 因此下面的寫法是沒有問題的。?
~~~
-?(void)?setDuration:(long?int)?newDuration?{??
duration?=?newDuration;??
}??
~~~
此外, 在構造方法中也必須要注意, 不能直接賦值title =newTitle, 而是要調用自身的設置方法:
~~~
//構造函數??
-(Song*)?initWithTitle:?(NSString?*)?newTitle?andArtist:?(NSString?*)?newArtist???
???????????andDuration:(long?int)newDuration?{??
????self?=?[super?init];??
????if?(?self?)?{??
????????[self?setTitle:newTitle];?????????
????????[self?setArtist:newArtist];??
????????[self?setDuration:newDuration];??
????}??
????return?self;??????
}??
~~~
assign 參數
assign參數代表設置時候直接賦值, 而不是復制或者保留它。?
這種機制非常適合一些基本類型, 比如NSInteger和CGFloat,?
或者就是不想直接擁有的類型, 比如委托。?
assign相當于如下寫法。
~~~
-?(void)?setTitle:(NSString?*)?newTitle?{??
title?=?newTitle;??
}??
~~~
retain參數
retain參數會在賦值時把新值保留(發送retain消息) 。?
此屬性只能用于Objective-C對象類型, 而不能用于基本數據類型或者Core Foundation。?
retain相當于如下寫法:
~~~
(void)?setTitle:(NSString?*)?newTitle?{??
[newTitle?retain];??
[title?release];??
title?=?[[NSString?alloc]?initWithString:?newTitle];??
}??
~~~
copy參數
copy在賦值時將新值拷貝一份, 拷貝工作由copy方法執行,
此屬性只對那些實行了NSCopying協議的對象類型有效。?
copy相當于如下寫法:
~~~
-?(void)?setTitle:(NSString?*)?newTitle?{??
[newTitle?copy];??
[title?release];??
title?=?[[NSString?alloc]?initWithString:?newTitle];??
}??
~~~
手工內存管理規則的總結:
1)如果需要保持一個對象不被銷毀,可以使用retain。在使用完對象后,需要使用release進行釋放。
2)給對象發送release消息并不會必須銷毀這個對象,只有當這個對象的引用計數減至0時,對象才會被銷毀。然后系統會發送dealloc消息給這個對象用于釋放它的內存。
3)對使用了retain或者copy、mutableCopy、alloc或new方法的任何對象,以及具有retain和copy特性的屬性進行釋放,需要覆蓋dealloc方法,使得在對象被釋放的時候能夠釋放這些實例變量。
4)在自動釋放池被清空時,也會為自動釋放的對象做些事情。系統每次都會在自動釋放池被釋放時發送release消息給池中的每個對象。如果池中的對象引用計數降為0,系統會發送dealloc消息銷毀這個對象。
5)如果在方法中不再需要用到這個對象,但需要將其返回,可以給這個對象發送autorelease消息用以標記這個對象延遲釋放。autorelease消息并不會影響到對象的引用計數。
6)當應用終止時,內存中的所有對象都會被釋放,不論它們是否在自動釋放池中。
7)當開發Cocoa或者iOS應用程序時,隨著應用程序的運行,自動釋放池會被創建和清空(每次的事件都會發生)。在這種情況下,如果要使自動釋放池被清空后自動釋放的對象還能夠存在,對象需要使用retain方法,只要這些對象的引用計數大于發送autorelease消息的數量,就能夠在池清理后生存下來。
自動引用計數(ARC):
強變量,通常,所有對象的指針變量都是強變量。
如果想聲明強變量,可以使用__strong Faction *fl;這樣的__strong來修飾。
值得注意的是,屬性默認不是strong,其默認的特性是unsafe_unretained類似assign。
所以需要聲明屬性strong時,可以如下:
@property (strong, nonatomic) NSMutableArray *birdNames;
編譯器會保證在事件循環中通過對賦值執行保持操作強屬性能夠存活下來。
帶有unsafe_unretained(相當于assign)或weak的屬性不會執行這些操作。
弱變量,可以使用__week關鍵字來聲明。弱變量不能阻止引用的對象被銷毀。
當引用的對象釋放時,弱變量會被自動設置為nil。
需要注意的是,在iOS4和Mac OS V10.6中不支持弱變量。
在這種情況下,你仍然可以為屬性使用unsafe_unretained, assing特性.
ARC都會在“底層”發生,所以一般不用關心。