1 理解引用計數
每個對象有個可增可將的計數器,ARC實際上也是一種引用計數機制,
- 引用計數工作原理
OC對象有個"保留計數"(retain count),或者"引用計數"(reference count).
NSObject協議聲明了三個方法
用于操作計數器,以遞增或遞減其值
Retain 遞增保留計數
release 遞減保留計數
autorelease 待稍后清理"自動釋放池"(autorelease pool)時,再遞減保留計數.
retainCount 查看保留計數的方法
為避免在不經意間使用了無效對象,一般調用完release之后都會清空指針,這就能保證不會出現肯呢個指向無效對象的指針,這種指針通常稱為"懸掛指針".
NSMutableArray *array = [NSMutableArray array];
NSNumber *number = @110;
[array addObject:number];
[number release];
number = nil;
- 屬性存取方法中的內存管理
- (void)setFood:(Food *)food{ if(food != _food){ [food retain]; [_food release]; _food = food; } }
自動釋放池
在OC的引用計數中.調用release會立刻遞減對象的標流計數,然而有時候可以不調用它,改為調用autorelease,此方法會在稍后遞減計數,通常是在下一次"循環事件"(event loop)時遞減.保留環
呈環狀相互引用的多個對象,這將導致內存泄漏,因為循環中的對象其保留技術不會將為0.對于循環中的每個對象來說,至少還有另外一個對象引用著它.通常采用弱引用來解決此問題
2 以ARC簡化引用計數
ARC下,我們不能直接調用retain,release,autorelease,dealloc等方法.實際上,ARC在調用這些方法時,并不通過普通的OC消息派發機制,而是直接調用其底層C語言版本.因為保留及釋放操作要頻繁執行,直接調用底層函數能節省很多CPU周期,比如,ARC會調用與retain等價的底層objc_retain.
- 有ARC之后,程序員無需擔心內存管理問題,使用ARC來編程,省去了類中的許多"樣板代碼"
- ARC管理對象生命期的辦法基本上就是:在合適的地方插入"保留"與"釋放操作".
在ARC環境下,變量的內存管理語意可以通過修飾符指明,而原來需要手工執行"保留"與"釋放操作". - 由方法所返回的對象,其內存管理語意總是通過方法名來體現,ARC將此確定為開發者必須遵守的規定
- ARC只負責管理OC對象,尤其要注意:CoreFoundation對象(純C的API生成的)不歸ARC管理,開發者必須適時調用CFRetain/CFRelease
3 在dealloc方法中只是否引用并解除監聽
在dealloc方法中一般:
- 釋放CoreFoundation對象,取消原來訂閱的"鍵值觀察"(KVO),注銷(unregister)NSNotificationCenter給此對象訂閱的某種通知,不要做其他事情
- (void)dealloc{ CFRealease(coreFoundationObject); [[NSNotificationCenter defaultCenter] removeObserver:self]; }
- 如對象持有文件描述符等系統資源, 在這種情況下如果非要等到系統調用dealloc方法時才釋放,那么保留這些稀缺資源的時間就有些過長了. 那么應該專門編寫一個方法來釋放此種資源, 這樣的類要和其他使用者約定: 用完資源后必須調用close方法.
- 執行異步任務的方法不應在dealloc里調用, 只能在正常狀態下執行的哪些方法 也不應在dealloc里調用.因為此時對象已處于正在回收的狀態了, 運行期已經改動了對象內部數據結構
- 在dealloc里 不要調用屬性的存取方法, 這種做法可能會令運行期系統的狀態完全失調,從而導致莫明錯誤
4 編寫"異常安全代碼"時留意內存管理問題
- 捕獲異常時,一定要注意將try塊內所創立的對象清理干凈
在MRC中,將釋放操作移到@finally(不管有沒有異常都會執行) - 在默認情況下,ARC不生成安全處理異常所需要的清理代碼,開啟編譯器標識后(-fobjc-arc-exceptions),可生成這種代碼.不過會導致應用程序變大,而且會降低運行效率.
默認不開啟的原因是,OC中只有程序終止才拋出異常,如果程序終止了,是否有內存泄漏都無關緊要了.
另外如果處于Objective-C++模式下,也會自動打開編譯器標識(-fobjc-arc-exceptions)
5 以弱引用避免保留環
對象圖里常會出現一種情況,就是幾個對象都以某種方式相互引用,從而形成"環"(cycle).由于OC內存管理模型使用.引用計數架構,所以這種情況通常會泄漏內存.
避免保留環的最佳方式就是弱引用,這種引用經常用來表示"非擁有關系"(nonowning relationship).將屬性聲明為weak
6 以"自動釋放池塊"降低內存峰值
OC對象的生命期取決于引用計數,在OC的引用計數架構中,有一項特性叫做"自動釋放池"(autorelease pool).
釋放對象有2種方式:
- 調用release方法,使其保留計數立即遞減
- 調用autorelease方法,將其加入"自動釋放池"中,自動釋放池用于存放哪些需要在稍后某個時刻釋放的對象
清空(drain)自動釋放池時,系統會向其中的對象發送release方法
創建自動釋放池的語法
@autoreleasepool{
//...
}
然而,一般情況下無須擔心自動釋放池的創建問題,iOS程序運行在CocoaTouch環境下. 系統會自動創建一些線程
比如說主線程,或者GCD機制中的線程, 這些線程默認都有自動釋放池,每次執行"事件循環"(event loop)時,就會
將其清空.因此不需要自己來創建"自動釋放池塊"
當從數據庫中多出許多對象的時候,代碼可能這么寫
NSArray *dataArray = [[DatabaseManager sharedInstance] getSportDataWithYear:currentDate.year andMonth:currentDate.month andDay:currentDate.day]; for (NSData *data in dataArray) { DBSportData *sportData = [[DBSportData alloc] initWithData:data]; [sportArray addObject:sportData]; }
在DBSportData初始化函數中,可能會再創建出一些臨時對象,若記錄有很多條,則內存中也會有很多不必要的臨時對象.他們都需要等待for循環完畢.自動釋放池執行下一次時間循環的時候才會清空.
我們可以增加一個自動釋放池即可解決此問題.
NSArray *dataArray = [[DatabaseManager sharedInstance] getSportDataWithYear:currentDate.year andMonth:currentDate.month andDay:currentDate.day];
for (NSData *data in dataArray) {
@autoreleasepool{
DBSportData *sportData = [[DBSportData alloc] initWithData:data];
[sportArray addObject:sportData];
}
}
自動釋放池機制就像"棧"(stack)一樣,系統創建好自動釋放池之后,就將其推入棧中,而清空自動釋放池,則相當于將其從棧中彈出. 在對象上執行自動釋放操作,就等于將其放入棧頂的哪個池中
在ARC之前,有一種老式的寫法,NSAutoreleasePool對象
//create a pool NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //do something //drain at the end [pool drain];
7 用"僵尸對象"調試內存管理問題
向已回收的對象發送消息是不安全的,這么做有時可以,有時又不行.具體可心與否,完全取決于對象所占內存有沒有為其他內容所覆寫,而這塊內存有沒有移做他用,又無法確定,以此,應用程序只是偶爾崩潰
Cocoa提供了"僵尸對象"(Zombie Object).啟用這項調試功能之后,運行期系統會把已經回收的實例轉換成特殊的"僵尸對象",而不會真正回收他們,這種對象所在的核心內存無法重用,因此不能遭到覆寫僵尸對象收到消息后,會拋出異常
通過Xcode開啟僵尸對象方法
#import <objc/runtime.h> @implementation Person void PrintClassInfo(id obj){ Class clas = object_getClass(obj); Class superCls = object_getClass([obj superclass]); NSLog(@"====%s:%s====",class_getName(clas),class_getName(superCls)); } int main(){
Person *person = [[Person alloc] init];
NSLog(@"Before release");
PrintClassInfo(person);
[person release];
NSLog(@"Before release");
PrintClassInfo(person);
} @end
控制器會打印
Before release ===Person:NSObject=== Before release ===_NSZombie_Person:nil===
對象所屬的類已由Person變為_NSZombie_Person
系統會修改對象的isa指針,另其指向特殊的僵尸類,從而使該對象變為僵尸對象,僵尸類能夠響應所有的選擇器,響應方式為:打印一條包含消息內容及其接受者的消息,然后終止應用程序
8 不要使用retainCount
- 對象的保留計數看似有用,實則不然,因為任何給定時間點上的"絕對保留計數"(absoulte retain count)都無法反映
對象生命周期的全貌 - 引入ARC之后,retainCount方法就正式廢棄了,在ARC下調用該方法會導致編譯器報錯