為什么要進行內存管理
- Objective-C不像JAVA和C#等語言,內存管理依賴垃圾回收(GC)機制;它需要開發者自己管理內存,即便目前的ARC機制也只是編譯器幫助開發者完成一部分工作,實際開發中還是需要時刻關注程序的內存相關;
- iOS程序員創建的對象大多分配在堆上,存儲空間有限,在iOS系統中如果app內存使用量過大,會收到內存預警的消息,不作處理的情況下系統可能會強制清理程序; 因此內存管理對移動端開發尤為重要;
如何進行內存管理
使用引用計數的方式對創建的對象進行內存的管理操作;有強引用指向(retain)那么引用計數+1,強引用被置為nil(release)那么引用計數-1;對象超過作用域該對象的引用計數如果為0,則系統會清理對象占用的內存空間,目前內存管理的方式分為MRC和ARC兩種.
- MRC:需要開發者編碼追加retain/release等消息;
- ARC:編譯器為開發者追加retain/release等消息;
引用計數是什么
- 內存管理中對引用自動計數的技術;
- 簡單的說是一個數值,可以通過對象的retainCount獲取 ;這個數值為0時代表給對象可以被系統回收;
- 引用計數采用散列表的方式存儲,對象的內存地址作為key,value就是對象的retainCount;
內存管理的思考方式
- 自己生成的對象自己持有(new、 alloc、 copy、 mutablecopy) ;
- 非自己生成的對象自己也能持有 ;
- 不需要自己持有的對象時釋放 ;
- 非自己持有的對象無法釋放 ,使用autorelease機制生成的對象,一旦持有就不需要自己釋放,一旦釋放就會導致崩潰問題;
autorelease的作用
- 生成的對象不自己持有,但能保證對象在超出作用域范圍存在并能正確地釋放
- 將對象添加到autoreleasepool中;
- 在對象超過autoreleasepool的作用域,向對象發送release消息;
- 像NSMutableArray的類方法+array內部生成對象就使用了autorelease;
autorelease的實現 (GNUStep)
- autorelease實例方法的本質就是調用NSAutoreleasePool的addObject:類方法 ;
- 關于autorelease的頻繁調用,系統的解決機制是使用"IMP Caching",每一個類都有一個方法緩存列表,這樣就能提高在運行時頻繁調用某個方法的效率 ;
autorelease的實現 (Apple)
autoreleasepool
- 使用autorealease標記的對象都會被注冊到自動釋放池中,當自動釋放池被drain時會向存儲的對象一一發送release消息
- autorealeasepool類似一個堆棧,提供pop();push();removeAll();方法。一次runloop循環就會創建一個autorealeasepool,事件循環結束自動釋放池銷毀,對象realease;引用計數為0的銷毀,不為0的可能會出現內存泄露的問題
手動創建autoreleasepool的情況
當開發中遇到在某個作用域內部產生大量的autorelease對象導致內存激增,需要考慮手動創建autoreleasepool來釋放局部變量的情況!
所有權修飾符
__strong
持有強引用的變量會在超過其作用域的時候被釋放;對應屬性中的retain/strong;默認情況下的所有權修飾符是__strong;
{
// 自己生成并持有對象
id _strong obj = [[NSObject alloc]init];
}// 超出作用域,強引用失效,釋放對象
__weak
- __weak修飾符解決的是兩個強引用對象互相引用導致的引用循環所引發的內存泄露問題;
- __weak修飾符與__strong 修飾符相反,不能持有對象;
- __weak修飾符持有的對象對釋放后,弱引用會被自動置為nil;
{
id obj = [[NSObject alloc]init];
id __weak weakObj = obj;
}
- 首先會調用函數objc_loadWeakRetained(&obj);
- retain一下obj;但不改變引用計數;
- obj_autorealease()將obj注冊到autorealeasepool中;
- 全局有一個可變字典;obj的內存地址作為key值,value是所有指向obj的weak指針列表(CFMutableSet);
- obj指向的內存塊銷毀了,對應的value中指針都為統一置為nil
__unsafe_unretained
和weak相似但不會在對象被銷毀時自動置為nil(屬性中對應assgin)
對于weak來說__unsafe_unretained性能上更加優越
__autoreleasing
類似調用autorelease方法將對象添加到自動釋放池中
內存管理開發tips
ARC與Block的內存管理
block為什么會導致循環引用?
當self對象持有block,在block中也持有self;在block中會copy一個self對象作為block的一個屬性;當要該屬性的釋放要等到block從堆中移除,而此時block要等待持有自己的self銷毀,由此導致循環引用;
解決方法:弱化self對象,但要在block內不防止弱化的對象過早釋放,由此在block中還得再次強化已弱化的self對象
屬性的set方法MRC下的內存管理的寫法
-(void)setName:(NSString*)newName{
[newName retain];
[_name release];
_name = newName;
自動釋放池和線程
Cocoa程序中的每一個線程,都維護自己的自動釋放池棧。如果你要寫一個Foundation程序,或者卸載一個線程,你需要創建自己的autorelease池塊。如果你的程序或者線程是常駐內存,并可能產生大量自動釋放對象,你應該使用自動釋放池(AppKit和UIKit在主線程中有自動釋放池);否則自動釋放對象累積,導致內存占用增長。如果你不是Cocoa中卸載線程,你不需要使用一個自動釋放池塊。
控制器移除時dealloc無法被調動
遇到這種情況,就需要排查控制器中出現的內存泄露了;
- delegate屬性類型是否被聲明為strong
- NSTime是否被被關閉
- block中是否造成了self的循環引用
- 是否存在兩個對象之間的強引用