Objective-C 記憶體管理

來源:互聯網
上載者:User
聲明

歡迎轉載,但是請尊重作者勞動成果,轉載請保留此框內聲明,謝謝。
文章出處:http://blog.csdn.net/iukey

大概是因為 Objective-C 是 C的超集,所以Objective-C 也使用alloc來申請記憶體,不同的是C調用free來直接釋放記憶體,而Objective-C 不直接調用dealloc來釋放記憶體。整個Objective-C 都使用對象的引用,而每個對象都有一個引用計數器。當計數器為0時,系統調用dealloc來釋放記憶體。Objective-C 業提供了autorelease 屬性,從而可以讓系統自動釋放對象所佔有的記憶體。程式中可以設定一個自動釋放池,系統使用這個池來跟蹤對象。使用如下語句可以建立一個自動釋放池:

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];

在我們的程式入口處,xcode也幫我們建了一個自動釋放池,而且把整個程式迴圈加了進去。

int main(int argc, char *argv[]){    @autoreleasepool {//這個說實話我也沒搞明白,不過我知道它建立了一個自動釋放池,然後把我們的程式放到了這個池子裡        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    }}//先別管這個的具體意思,你只要知道xcode幫我們建了一個自動釋放池,然後把整個程式放到了自動釋放池裡面。

在這個 自動釋放池建立之後,基礎架構就自動將數組、字串等對放到這個池中。當下面語句被執行時,池中的對象就被自動釋放:

[ pool drain];

總之,一個對象被標識為自動釋放對象,那麼就會被加到自動釋放池中。除了使用drain來釋放池中的對象外,在自動釋放池本身被釋放時,池中的所有對象也會被釋放。自動釋放池語句如下:

[ pool release ];

之前是沒有ARC(Automatic Reference Counting)功能的,現在有了,我想ARC 主要是為從Java轉過來的程式員而設計的吧,因為這個差不多相當於Java裡面的自動垃圾收集器吧。從C/C++轉過來的程式員大多數還是願意手動去釋放的,因為他們不大相信自動釋放的效率,還有就是他們喜歡掌控一切的感覺,比如我。如果你要開啟,在建立工程的時候請勾選上這個選項:

如果你使用手動管理記憶體,請往下看:

一、申請記憶體(alloc)

當使用 alloc 建立了一個對象時,需要在用完這個對象後釋放(release)它,你不需要自己去釋放一個被設定為自動釋放(autorelease)的對象,如果你真的這麼幹了,對不起,程式會崩潰。

   //str1 是autorelease的,會自動釋放    NSString* str1 = [NSString string];    //str2需要手動釋放    NSString* str2 = [[NSString alloc]init];    //str2 使用完後    [str2 release];

為便於理解,你可以理解成autorelease型的對象會在該釋放他的時候自動釋放。

二、釋放記憶體(dealloc)

當一個對象從記憶體上刪除之前,系統就會調用dealloc方法,這是釋放對象成員變數的最好時機。比如:

- (void)dealloc{    [_window release];    [super dealloc];}

上例中,先調用release釋放了成員變數_window所佔用的記憶體。相對而言,用標準的release比autorelease更快一點。最後一行[ super dealloc ]; 非常重要,必須調用這個方法讓父類清楚它自己,否則會造成記憶體流失。

在ARC模式下,dealloc 不會被調用到,取而代之的是,需要實現finalize方法 。

三、引用計數器(retainCout)

整個Objective-C都使用對象引用,而每個對象有一個引用計數器。當時用alooc(或copy)方法建立一個對象時。其計數器的值為 1 。當計數器為 0 時,系統自動調用 dealloc方法來釋放記憶體中的對象。比如:

STU *stu = [[STU alloc]init];//計數器為1    [stu retain];//計數器為2    [stu release];//計數器為1    [stu release];//計數器為0,系統自動調用dealloc方法    //釋放之後,如果調用該對象的任何一個方法,程式就會異常終止    [stu goHiking];//崩潰

為了防止上述的異常崩潰出現,可以在最後一個release之後加一句:

 stu =nil;

上述語句將該對象置為nil。在這之後,任何調用該member的方法都返回nil,而不是異常終止。再來看下面的例子:

int main(int argc, char *argv[]){    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];        NSNumber* int1 = [NSNumber numberWithInt:222];    NSNumber *int2;    NSMutableArray* arr1 = [NSMutableArray array];    NSLog(@"初始化以後:int1(retainCount):%lu int2(retainCount):%lu",(unsigned long)[int1 retainCount],(unsigned long)[int2 retainCount]);        [arr1 addObject:int1];    NSLog(@"將int1添加到數組以後:int1(retainCount):%lu int2(retainCount):%lu",(unsigned long)[int1 retainCount],(unsigned long)[int2 retainCount]);        int2 = int1;    NSLog(@"進行賦值操作以後:int1(retainCount):%lu int2(retainCount):%lu",(unsigned long)[int1 retainCount],(unsigned long)[int2 retainCount]);        [int1 retain];    NSLog(@"int1 retain以後:int1(retainCount):%lu int2(retainCount):%lu",(unsigned long)[int1 retainCount],(unsigned long)[int2 retainCount]);        [int1 release];    NSLog(@"int1 release以後:int1(retainCount):%lu int2(retainCount):%lu",(unsigned long)[int1 retainCount],(unsigned long)[int2 retainCount]);        [arr1 removeObject:0];    NSLog(@"從數組中刪除int1以後:int1(retainCount):%lu int2(retainCount):%lu",(unsigned long)[int1 retainCount],(unsigned long)[int2 retainCount]);        [pool drain];    return 0;}

程式結果:

初始化以後:int1(retainCount):1 int2(retainCount):0

將int1添加到數組以後:int1(retainCount):2 int2(retainCount):0

進行賦值操作以後:int1(retainCount):2 int2(retainCount):2

int1 retain以後:int1(retainCount):3 int2(retainCount):3

int1 release以後:int1(retainCount):2 int2(retainCount):2

從數組中刪除int1以後:int1(retainCount):1 int2(retainCount):1

基礎架構所提供的一些方法也會增加對象的引用次數,比如,把對象添加到一個數組中的時候。

在上面的例子中,首先將NSNumber對象int1的值設定為整數222,我們定義了int2但是沒有初始化。這時候列印引用計數器,會發現int1的計數器為1,int2的計數器為0。也就是說,只有真正建立了一個對象,並且正常初始化了之後,才會增加引用計數器。

隨後使用addObject:方法將int1添加到數組arr1中,列印計數器,不難發現int1的計數器變為2,int2的計數器還是0。也就是說當對象添加到任何類型的集合中時,都會使改對象的有效引用增加。

接下來,將int1的值賦給int2,這時候的結果值得我們注意。int1與int2的引用計數器都是2了,這說明進行賦值操作並未使int1的計數器增加,但是int2的計數器變為了2。這是因為當把int1賦給int2的時候,並不是複製實際的對象,而是將該對象的記憶體地傳遞給了int2。也就是說這兩個指標指向的是同一個對象。

然後我們向int1發送retain訊息來增加引用計數,int2的引用計數器當然也隨之增加咯。

然後向int1發送release訊息,int1與int2的引用計數當然同時減 1 咯。

最後,從arr1中移除 int1 ,這樣int1與int2的引用計數又同時減 1 。但是這時候對象並未真正釋放,因為引用計數器 1,並不是0。如果要釋放你還得 release一次。

Objective-C的記憶體管理系統基於引用計數。我們需要跟蹤引用,以及在運行期內是否真的釋放了記憶體。簡單來說就是每次調用了alloc或者retain之後都需要調用release。

在程式中,你只在兩種情況下跟蹤一個對象:(1)本地變數(在一個方法裡面臨時使用,比如建立一個字串)(2)成為一個類的成員變數只要掌握了跟蹤者兩種情況,並作出相應記憶體釋放,你就基本掌握了Objective-C的記憶體管理。

三、字串處理

如果使用alloc或者copy建立一個對象(比如字串),那麼在方法結束的時候需要release或者autorelease這個對象。假如我們是通過別的方式建立的,就可以不管。在下面的粒子中,只需要release那些使用alloc建立的對象:

 NSNumber* num1 = [[NSNumber alloc]initWithFloat:1.80];    NSNumber* num2 = [NSNumber numberWithFloat:145.0];    //我們只需要釋放num1,不用釋放num2    [num1 release];

再看下面的例子:

int main(int argc, char *argv[]){    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];        NSString* str1 = @"LiuWei";    NSString* str2 = [NSString stringWithString:@"IUKEY"];    NSMutableString* str3 = [NSMutableString stringWithString:@"MasterLiuWei"];    NSMutableArray* arr1 = [NSMutableArray array];    //初始化以後 各個retainCount:str1:fffffffffffffff str2:fffffffffffffff str3:1        [arr1 addObject:str1];    [arr1 addObject:str2];    [arr1 addObject:str3];    //將字串添加到數組以後,各個retainCount:str1:fffffffffffffff str2:fffffffffffffff str3:2      [str1 retain];    [str2 retain];    [str3 retain];    //執行retain以後,各個retainCount:str1:fffffffffffffff str2:fffffffffffffff str3:3        [str3 release];    //執行release以後,各個retainCount:str1:fffffffffffffff str2:fffffffffffffff str3:2        [pool drain];    return 0;}

你可能會對結果產生疑問,為什麼是fffffffffffffff 呢?在程式中,NSString 的對象 str1 賦值為靜態字串@"welcome"。字串常量的記憶體配置方式和其他的對象不同,這種方式沒有引用計數機制,因此永遠不能釋放這些對象。當向 str1 發送訊息 retainCount 的時候,它返回fffffffffffffff ,即最大的不帶正負號的整數。注意:這同樣適用於用字串常量初始化那些不可變的字串對象。

值得注意的是下面的語句:

NSMutableString* str3 = [NSMutableString stringWithString:@"MasterLiuWei"];

這條語句使用字串常量來初始化一個可變字串對象,通過 stringWithString: 方法進行。由於可變字串的至可能在程式運行過程中發生變化。但是字串常量是無法改變的,所以系統拷貝這個字串常量到 str3 上來完成初始化。因此可變字串對象擁有了引用計數。

當我們使用stringWithString:  方法建立不可變字串對象時,這個對象其實被添加到了自動釋放池。通過array 方法所建立的對象arr1 對象也被添加到了自動釋放池。

程式中的代碼塊將這些字串添加到數組中,冰箱這給謝對象發送retain 訊息。從程式的結果可以看出,這些操作改變了他們的引用技術。

在釋放自動釋放池之前,str3已經被釋放了一次。所以 str3 的引用計數變為2。隨後,自動釋放池的釋放使得這些對想的引用技術減為 0 ,這使得他們被釋放。當釋放自動釋放池的時候,池中的每一個對象都會收到一條 release 訊息(收到的條數等於當時發送autorelease訊息的條數)。由於在使用 stringWithString: 方法所建立的字串對象 str3 時,系統將他添加到自動釋放池,也就是說調用了 autorelease
方法,所以,當自動釋放池被釋放(drain)時,str3會收到一條  release訊息,這將導致他的引用計數減為1。當自動釋放池中的數組被釋放的時候,數組中的每個元素也將被釋放,所以數組中的每個元素都將收到一條release'訊息,這將使str3的引用計數變為0 。系統隨之將調用str3的dealloc方法將str3釋放。

注意:如果我們過度釋放,程式將異常終止。

四、類成員變數

在大多數情況下,一個成員變數的setter方法應該僅僅 aytorelease/release 舊的對象,然後 retain/copy 新的對象。我們只需要在 dealloc的時候調用release就好了。所以真正需要管理的就是方法內部的局部變數的引用。比如,設定字串(使用copy):

- (void)setName:(NSString*)newName{    if (name != newName) {        [name release];        name = [newName copy];//name 的計數器為1    }}

下面是使用retain管理成員對象的另一個例子:

- (void) setScore:(NSNumber*)inputScore{    [score autorelease];    score = [inputScore retain];}
- (void)dealloc{    [score release];    [super dealloc];}

下面是使用本機物件去設定一個成員變數的例子:

  NSNumber* value1 = [[NSNumber alloc]initWithFloat:180.0];    [self setScore:value1];        NSNumber* value2 = [NSNumber numberWithFloat:72.5];    [self setScore:value2];    [value1 release];

下面我來分析一下幾種成員變數的處理方法。

首先定義一個類,它只包含NSString類型的name屬性,並手動書寫了這個屬性的存取方法。

#import <Foundation/Foundation.h>@interface Student : NSObject{    NSString* name;}-(void)setName:(NSString*)s;-(NSString*)name;@end

在下面的方法實現檔案中,我們直接把setName: 方法中的形參 s 的值賦給 name 屬性,並沒有做其他的操作。

#import "Student.h"@implementation Student-(void)setName:(NSString *)s{    name =s;}-(NSString*)name{    return name;}@end

下面是測試代碼:

NSMutableString* str1 = [NSMutableString stringWithString:@"LiuWei"];    Student* stu = [[Student alloc]init];        NSLog(@"str1的引用計數器:%x",[str1 retainCount]);        [stu setName:str1];    NSLog(@"str1的引用計數器:%x",[str1 retainCount]);    [stu release];

測試結果:

str1 的引用計數器:1

str1 的引用計數器:1

程式首先為Student類建立了對象stu ,然後調用這個對象的setName 方法,將name的值設定為  str1的值(str1 和name 這兩個指標指向的是同一塊記憶體空間)。這種做法有一個問題,在設定完畢以後,如果不需要str1對象並將其釋放,那麼str1的 引用計數器變為0。毫無疑問,name的引用計數器也將變為0 ,儲存在那麼中的值則變為無效的。

由於str1是用 NSMutableString stringWithString: 方法建立的,該對象也被自動添加到自動釋放池之中(雖然沒有顯式將對象添加到 自動釋放池中)。因此,在自動釋放池釋放的時候,str1 也會被釋放,這時,任何str1所指向的對象的訪問都將是無效的。

下面我們來看第二種寫法。介面檔案未改動,所以參看上面的介面代碼。在實現檔案中我們執行了 [ name retain] 方法,這樣會使 name 的引用計數器增加 1 ,從而在str1 釋放的時候,name 還能夠正常使用。

#import "Student.h"@implementation Student-(void)setName:(NSString *)s{    name =s;    [name retain];}-(NSString*)name{    return name;}@end

再來測試:

NSMutableString* str1 = [NSMutableString stringWithString:@"LiuWei"];    Student* stu = [[Student alloc]init];        NSLog(@"str1的引用計數器:%x",[str1 retainCount]);        [stu setName:str1];    NSLog(@"str1的引用計數器:%x",[str1 retainCount]);    [stu release];    NSLog(@"str1的引用計數器:%x",[str1 retainCount]);

測試結果:

str1 的引用計數器:1

str1 的引用計數器:2

str1 的引用計數器:1

根據結果,在調用 setName: 之後,str1的引用計數器變為2 ,上一個程式中所產生的問題得以解決。接著就在程式中執行了 [str1 release] 方法,使得 str1 的引用計數器減少為 1,這時候對name的引用依然有效。

由於程式使用了alloc方法建立 Student 類的對象,就需要負責將這個對象釋放掉。我們可以手動釋放,也可以調用 它的autorelease 方法,將其添加到自動釋放池中。在這之前我們都能正常使用這個對象。

但是這樣的寫法還是存在一些問題,setName: 方法保持了作為參數傳入的字串對象,但是這個對象什麼時候釋放呢?當多次 更改name 值的時候,原來的那些值會怎樣?應該釋放他們所佔用的記憶體嗎?

下面來看最終的處理方法:

//  Student.h#import <Foundation/Foundation.h>@interface Student : NSObject{    NSString* name;}-(void)setName:(NSString*)s;-(NSString*)name;- (void)dealloc;@end
//  Student.m#import "Student.h"@implementation Student-(void)setName:(NSString *)s{    [name autorelease];    name = [s retain];}-(NSString*)name{    return name;}- (void)dealloc{    [name release];    [super dealloc];}@end

測試代碼:

 NSMutableString* str1 = [NSMutableString stringWithString:@"LiuWei"];    Student* stu = [[Student alloc]init];        NSLog(@"str1的引用計數器:%x",[str1 retainCount]);        [stu setName:str1];    NSLog(@"str1的引用計數器:%x",[str1 retainCount]);

測試結果:

str1 的引用計數器:1

str1 的引用計數器:2

通過上面的方法,不管name中當前儲存的值是什麼,我們在 setName: 方法中首先將其手動添加到自動釋放池中,這也可以讓其以後自動釋放 name 。當程式中多次調用 setName: 方法時,這項操作將更加重要:每次儲存新的值的時候,變數就得值將標記為自動釋放,保持新的值到name中。

當系統釋放一個對象時,系統自動調用dealloc 方法。對於保持(retain)的對象,或者使用alloc分配的對象,或者在方法中複製的對象,可以在dealloc方法中去釋放它們。首先釋放name變數,因為他是保持對象:然後調用父類的dealloc方法來釋放Student 的對象。如果需要外部的字串完全獨立於設定方法的參數,可以在設定方法中聲稱字串的全新副本,即使用copy選項。

五、自動釋放( autorelease )池

 在代碼中建立了一個字串,如果需要返回這個字串的話,那麼需要使用 autorelease,而不是release。比如:

- (NSString*)getStr{    NSString* str;    str = [[NSString alloc]initWithFormat:@"Result return "];    //[ str release];  //不能釋放,否則調用者無法獲得傳回值    [str autorelease];//正確    return str;}

通過使用autorelease,該對象就被放入自動釋放池,系統自動跟蹤每個對象的使用方式,並在

釋放自動釋放池時, 釋放池中的所有對象

注意:autorelease 不是垃圾搜集(Garbage Collection)功能。在iPhone 作業系統上的Objective-C沒有垃圾搜集功能。另外,你可以建立多個自動釋放池。

int main(int argc, char *argv[]){    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];    Student* stu =[[Student alloc]init];//1    NSLog(@"%x",[stu retainCount]);        [pool drain];    NSLog(@"%x",[stu retainCount]);//1        pool =[[NSAutoreleasePool alloc]init];    [stu autorelease];    NSLog(@"%x",[stu retainCount]);//1        [stu retain];    NSLog(@"%x",[stu retainCount]);//2        [pool drain];    NSLog(@"%x",[stu retainCount]);//1    [stu release];        return 0;}

六、記憶體流失

為了防止記憶體流失,並確保最有效地使用記憶體,應用程式應該只在需要時才裝載資料。,比如,使用者點擊了某個按鈕才顯示相關資料。我們在前面講了autorelease ,但是如果你的代碼總是 autorelease,就沒有記憶體流失了嗎?問題在於autorelease 池釋放的時機。每當執行應用程式時,系統自動建立 autorelease 池。系統並不是立即釋放autorelease池中的對象,而是在一個run loop之後才釋放,一般是微妙層級。在
autorelease 池中的對象本可以立即釋放,但是系統很有可能過一段時間才釋放他們,所以 使用release 可以更有效地釋放記憶體。所以,我們應該盡量自己管理記憶體,不要太依賴 autorelease 。另外,一些系統對象使用 autorelease,如NSString。你可以 ziji管理記憶體的類,如NSMutableString,你可以建立自己的池。

下面是幾個記憶體管理的基本原則:

1>如果使用 alloc(或者copy)方法建立一個對象,或者使用retain 保留一個對象,那麼 ,都要自己釋放對象。

2>在大多數情況下,申請記憶體的語句數量和釋放記憶體的語句數量應該相等

3>盡量少使用記憶體,用完立即釋放

七、copy、nonatomic

對於字串類型的屬性變數,我們經常使用下面類似的語句:

@property(nonatomic,copy)NSString* name;

這個語句就等價於:

-(void)setName:(NSString *)s{    if (s != name) {        [name release];        name = [s copy];    }}

那麼為什麼要使用copy呢?如果直接使用下面的語句,結果將如何呢?

-(void)setName:(NSString *)s{    name = s;}

結果是name 和 s 都指向同一個對象。當在調用 setName: 方法之後,如果 s 的值被修改,這顯然不是我們想要的結果。所以,使用copy 來拷貝 s 的值到name 上,其完成的功能就是調用一個alloc 方法來建立一個新的字串對象 (initWithStringLs).

在多線程中,兩個或多個線程可能在同一時間執行同一代碼。為了防止這種現象發生,開發人員可以使用互斥鎖。nonatomic 的意思是不需要使用互斥鎖, atomic 是使用互斥鎖。預設 是atomic 。 如果你的程式並沒有多線程,可以設定為 nonatomic 以節省資源。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.