淺談 Objective-C 的內存管理

       在學習 iOS 開發過程中,我們時常被對象的初始化和釋放所困擾,由此引發軟件性能的不穩定,crash 時有發生,但又不易排查。當你的程序創建出一個對象,對象會佔內存,你要在對象不被使用後釋放出內存空間。 也就是說,當對象不再需要時,要記着及時釋放它。當然, 有時很難確定一個對象是否還會被使用, 比如 程序執行過程中,你的對象會被多個其他對象所引用,當被其他對象引用的可能性還存在時,就不能是否這個對象, 否則, 這樣做可能會導致程序崩潰(crash)或出現不可預期的結果。

       爲了幫助你處理那些不再被使用的對象, cocoa touch  爲每個對象關聯了一個計數器, 被稱爲 “保留計數器(retain counter)”。 當爲對象增加一條引用信息時,就讓對象在它的計數器里加一;當減少一次引用,則減一。 當保留計數器的計數爲0時,對象就知道自己不再被引用了,此時可以被安全地毀掉了,這時候的對象會毀掉自己,並釋放出內存空間。

     有了保留計數器,對象就知道自己被引用多少次。要使對象的保留計數器增加數值,要做的就是發送一條“保留信息(retain message)” ;減少計數器數值則要發送“釋放信息(release message)”。 爲確保對象的釋放不被遺漏,建議你只要用到了 retain,一定要有對應的 release 操作。

    如此看來,維護 reference counting 是一筆不小的開銷。 爲解決這個問題, 蘋果在Xcode 4.2 之後 (即 iOS 5版本) 推出了 ARC (Automatic Reference Counting)概念。 詳見:  http://developer.apple.com/technologies/ios5/

Automatic Reference Counting
Automatic Reference Counting (ARC) for Objective-C makes memory management the job of the compiler. By enabling ARC with the new Apple LLVM compiler, you will never need to type retain or release again, dramatically simplifying the development process, while reducing crashes and memory leaks. The compiler has a complete understanding of your objects, and releases each object the instant it is no longer used, so apps run as fast as ever, with predictable, smooth performance.

對應中文如下:

自動引用計數

Objective-C 的自動引用計數(Automatic Reference Counting,ARC)使得內存管理成爲編譯器的工作。如果激活了新的 Apple LLVM 編譯器的 ARC 功能,您將再也不用輸入 retain 或者 release,極大地簡化了開發過程,同時減少了程序崩潰和內存泄露的可能性。編譯器瞭解對象的整個生命週期,並且在對象不再被使用的時候釋放它,所以程序運行的和以前一樣快,甚至有一些性能上的提高。

     需要說明的是: 如果是通過手工 alloc 的方式創建一個對象,在使用完後,需要用release 釋放這個對象。 而通過自動釋放(autorelease) 方式聲明的對象, 便可不管。 若仍用 release 方法來釋放, 這將使得應用程序崩潰。

//string1 將被自動釋放 
        NSString* string1 = [NSString string];

release 和 autorelease 的區別, 標準的release會立刻釋放對象的引用。autorelease會等一會兒才釋放。
        //必須在用完後手工釋放
       NSString* string2 = [[NSString alloc] init];
        .....

[string2 release];

在.m 文件中,經常看到如下的聲明:

‐ (void) dealloc
      {
          [ var1 release];

    .......
          [super dealloc];
      }

通過 @interface 聲明一個對象, 對象中又包含多個實例變量。 也就是說,實例變量是對象的成員。 當一個對象從內存中刪除時,dealloc 方法將被調用。 這個方法要做的事情就是釋放對象中的所有實例變量。 這裏要特別注意 [super dealloc] 方法的調用。[super dealloc] 是向父類發送了一個消息,要求父類做清理工作。如果不這樣做的話,該對象就不會從內存中刪除,從而造成了內存泄露。

在實際應用中,通常只有兩個原因,我們纔會創建一個對象:

1. 作爲一個實例變量保留;

2. 在函數內部作爲臨時變量使用。

對應第一種情況,只需要保證在dealloc 函數中釋放(release)它就行了。 我們實際要做的是管理好函數內部的臨時變量的引用,記住一個原則: 只要是通過alloc或copy 創建的對象,在函數結尾的地方,一定要發送一個 release 或autorelease 消息; 如果是通過其他方式創建的對象,就可以放手不管了。

 

繼續實例說明:

 聲明一個子函數, 將數字存儲爲字符串格式,如下:

- (NSString *)storedNumberAsString

{

NSString *stringToReturn = [[NSString alloc] initWithFormat:@"%f", storedNumber];

return stringToReturn;

}

乍一看,這個函數實現了我們想要的功能,但疏漏之處在於沒有解決對象釋放問題。通常,只要用到了alloc, 就應該有一個相應的 release。 修改如下,看看怎樣:

- (NSString *)storedNumberAsString

{

NSString *stringToReturn = [[NSString alloc] initWithFormat:@"%f" , storedNumber];

[stringToReturn release];

return stringToReturn;

}

修改之後,的確實時釋放了,釋放的時序有誤。在對象返回之前被釋放,這意味着返回值已經沒有了意義。

繼續修改, 如下: 

- (NSString *)storedNumberAsString

{

NSString *stringToReturn = [[NSString alloc] initWithFormat:@"%f" , storedNumber];

return stringToReturn;

[stringToReturn release];  

}

修改之後, 先返回對象,緊接着釋放對象,這在邏輯上有問題。 在同一個子函數中,返回之後的語句已經不再起作用了。 也就是說,這個釋放形同虛設。

 我們在子函數中 分配並初始化了對象, 自然釋放問題也應該由子函數完成,不能把這個“責任”推卸 子函數的調用者。 有一種方法可解決這個問題, 通過自動釋放(autorelease)的方法。 修改如下: 

- (NSString *)storedNumberAsString

{

NSString *stringToReturn = [[NSString alloc] initWithFormat:@"%f" , storedNumber];

return [stringToReturn autorelease];

}

 

這裏列舉一個實例,在一個textview 中,將數字顯示爲字符串。 其實現函數如下:

- (IBAction)displaySomeText:(id)sender

{

WonderfulNumber *myWonderfulNumber = [[WonderfulNumber alloc] init];

[myWonderfulNumber setStoredNumber:pi];

NSString *numberString = [myWonderfulNumber storedNumberAsString];

 

[textView insertText:numberString];

[myWonderfulNumber release];

}

這裏需要特別注意   [[WonderfulNumber alloc] init] 的init使用方法。在調試代碼時,你會發現,即使省去init, 編譯也沒問題。 但從apple 資料中查知, init 還是需要的。 [[WonderfulNumber alloc]  這個方法是爲對象分配了內存,緊接着,調用init 方法,對該對象進行初始化

發佈了8 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章