標籤:objective-c ios 記憶體管理
一、記憶體管理的?式
大家都去過圖書館,而圖書館裡的書是可以借出的。我們來設想這樣一個情境,大家都去借書,但是從來沒有人去還書,那麼最後,這個圖書館會因為無書可借而倒閉,每個人都沒法再使用圖書館。電腦也是這樣,當程式運行結束時,作業系統將回收其佔用的資源。但是,只要程式運行就會佔用資源,如果不進行清理已經不用的資源,資源最終將被耗盡,程式將崩潰。
學會記憶體管理我們就明白什麼時候由你釋放對象,什麼時候你不能釋放。C語言中通過malloc、calloc、realloc和free搭配對記憶體進行管理。但是,C中的記憶體管理極易引起記憶體泄露和野指標異常(有關C語言記憶體泄露的問題,不是本博文的重點,對此不做過多贅述。詳情會在後續的博文中專門講解C記憶體問題。),而Objective-C對此進行了最佳化。
iOS應用程式出現Crash(閃退),90%以上的原因是記憶體問題。在一個擁有數?個甚至是上百個類的工程裡,尋找記憶體問題極其困難。瞭解記憶體常見問題,能幫我們減少出錯幾率。記憶體問題體現在兩個?面:記憶體溢出、野指標異常。
記憶體溢出
iOS給每個應?程式提供了一定的記憶體,?於程式的運行。iPhone 3GS記憶體30M左右,iPhone 5S記憶體80M左右。一旦超出記憶體上限,程式就會Crash。程式中最占記憶體的就是圖?、?頻、視頻等資源檔。3.5寸非Retina(視網膜)螢幕(320*480)放一張全屏圖片,佔用位元組數320*480*4(一個像素佔4個位元組,存放RGBA),即:600k Bytes。iPhone 3GS同時讀取60張圖片就會Crash。4寸螢幕(320*568),實際像素640*1136,程式存放一張全屏圖片,佔用位元組數640*1136*4,即2.77M Bytes。iPhone 5S同時讀取40張圖片就會Crash。
野指標異常
對象記憶體空間已經被系統回收,仍然使?指標操作這塊記憶體。野指標異常是程式crash的主要原因。代碼量越?的程式,越難找出野指標的位置。瞭解記憶體管理,能幫我們提升程式效能,?大減少調試bug時間。
記憶體管理的?式
記憶體回收(gc):程式員只需要開闢記憶體空間,不需要用代碼顯示地釋放,系統來判斷哪些空間不再被使用,並回收這些記憶體空間,以便再次分配。整個回收的過程不需要寫任何代碼,由系統自動完成記憶體回收。Java開發中一直使?的就是記憶體回收技術。
人工引用計數 MRC(Manual Reference Count):記憶體的開闢和釋放都由程式碼進?控制。相對記憶體回收來說,對記憶體的控制更加靈活,可以在需要釋放的時候及時釋放,對程式員的要求較高,程式員要熟悉記憶體管理的機制。
?動引用計數 ARC(Auto Reference Count):Xcode4.2及以上版本具有自動管理記憶體ARC機制,iOS 5.0的編譯器特性,它允許使用者只開闢空間,不用去釋放空間。它不是記憶體回收。本質是MRC,只是編譯器協助程式員預設加了釋放的代碼。
iOS的記憶體管理
- iOS支援兩種記憶體管理方式:ARC 和 MRC。
- MRC的記憶體管理機制是:引用計數。
- ARC基於MRC。
二、記憶體管理機制及影響引用計數的方法引用計數
實際開發中,可能會遇到兩個以上的指標使用同一塊記憶體。C語言無法記錄記憶體使用量者的個數。Objective-C採用引用計數機制管理記憶體,當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放佔有的資源。
影響引?計數的方法
+ alloc:開闢記憶體空間,讓被開闢的記憶體空間的引用計數變為1。這是由0到1的過程。
- retain:引?計數加1,如果記憶體空間之前引用計數為1,retain之後變為2,如果引用計數是5,retain之後變為6。
- copy:把某一記憶體地區的內容拷貝一份,拷貝到新的記憶體空間裡去,被拷貝地區的引用計數不變,新的記憶體地區的引用計數為1。copy可以這樣理解,如果指標A和指標B不想互相牽扯,A管理A的記憶體,B管理B的記憶體。
- release:引用計數減1,如果記憶體空間之前引用計數為4,release之後變為3,如果之前引用計數為1,release之後變為0,記憶體被系統回收。
- autorelease:未來的某一時刻(程式運行出自動釋放池)引用計數減1。如果記憶體之前引用計數為4,autorelease之後仍然為4,未來某個時刻會變為3。
- dealloc:繼承自父類的方法,當對象引用計數為0的時候,由對象自動調用。在dealloc方法中對變數的釋放順序與初始化的順序相反,在最後調用[super init]。
autoreleasepool的使?
通過autoreleasepool控制autorelease對象的釋放。
向一個對象發送autorelease訊息,這個對象何時釋放,取決於autoreleasepool。
NSAutoreleasePool *pool= [[NSAutoreleasePool alloc] init];Person *p = [[Person alloc] init]; // retainCount為1[p retain]; // retainCount為2[p autorelease]; // retainCount為2 未來的某個時刻釋放[pool release]; // 此時 autorelease的對象引用計數-1NSLog(@”%d”, [p retainCount]); // 列印結果為1/* NSAutoreleasePool在ARC模式下運行,會得到編譯錯誤,而不是運行錯誤。作為替代,@autoreleasepool{}被引入。本例只是實現簡單效果。 */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
和 [pool release];
就像一對括弧,[xxx autorelease];必須寫在兩者之間。[xxx autorelease];出現在了兩者之間,pool就會把接收autorelease的對象給儲存起來(以棧的方式,把對象壓入棧)當[pool release];的時候,pool會向之前儲存的對象逐一發送release訊息(對象出棧,越晚autorelease的對象,越早接收release訊息)。
在iOS 5之後,不再推薦使用NSAutoreleasePool類,使用@autoreleasepool{}替代。之前寫在NSAutoreleasePool *pool = [[NSAutoreleasePool
alloc] init];
和 [pool release];
之間的代碼,需要寫在@autoreleasepool{}
的大括弧裡。出了大括弧,自動釋放池才向各個對象發送release訊息。
三、記憶體管理的原則
引用計數的增加和減少相等,當引用計數降為0之後,不應該再使?這塊記憶體空間。凡是使用了alloc、retain或者copy讓記憶體的引用計數增加了,就需要使用release或者autorelease讓記憶體的引?計數減少。在一段代碼內,增加和減少的次數要相等。
四、copy
跟retain不同,一個對象想要copy,?成?己的副本,需要實現NSCopying協議,定義copy的細節(如何copy)。如果類沒有接受NSCopying協議而給對象發送copy訊息,會引起crash。
copy分為深copy和淺copy。我們經常使用(包括下面例子)的是淺copy,至於區別會在後續的博文中詳細敘述。
copy?法的實現
Person.h?件
@interface Person : NSObject<NSCopying>@property (nonatomic, retain) NSString *name;@property (nonatomic, assign) int age;@end
Person.m?件
@implementation Person- (id)copyWithZone:(NSZone *)zone{ Person *p = [[Person allocWithZone:zone] init]; p.age = self.age; p.name = self.name; return p;}
main.m?件
Person *p = [[Person alloc] init];p.name = @”張三”;p.age = 20;Person *p2 = [p copy];// p2是p的副本。p2.name與p.name一樣。p2.age與p.age一樣。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Objective-C學習筆記_記憶體管理(一)