IOS 記憶體管理

來源:互聯網
上載者:User

小議iphone記憶體管理與屬性

 

一、前言

      對於大多數從C++或者JAVA轉過來學習Object-C(以下簡稱OC)的人來說,OC這門語言看起來非常奇怪,用起來也有點麻煩。

      OC沒有像JAVA一樣的記憶體回收機制,也就是說,OC編程需要程式員手動去管理記憶體。這就是為什麼它煩的原因,蘋果卻一直推崇開發人員在有限硬體資源內寫出最佳化的代碼,使用CPU最少,佔用記憶體最小。

 

二、基本原理

對象的建立:

      OC在建立對象時,不會直接返回該對象,而是返回一個指向對象的指標,因此出來基本類型以外,我們在OC中基本上都在使用指標。

      ClassA  *a = [[ClassA   alloc]  init];

      在[ClassA   alloc]的時候,已經發送訊息通知系統給ClassA的對象分配記憶體空間,並且返回了指向未初始化的對象的一個指標。

      未初始化的ClassA對象接手到init訊息,init返回指向已初始化後的ClassA對象的一個指標,然後將其賦值給變數a。

在建立並使用完一個對象的時候,使用者需要手動地去釋放該對象。

      [a   dealloc];

如果指標a和b同時指向堆中同一塊記憶體位址

      ClassA  *a = [[ClassA   alloc]  init];

      ClassA  *b = a;

      [a   dealloc];

當執行到第三行的時候,指標b就成了無頭指標。這是一個在C++中也是常見的錯誤,我們需要避免這類錯誤,因為無頭指標是危險的。

引用計數:

      OC在記憶體管理上採用了引用計數(retain count),在對象內部儲存一個數字,用來表示被引用的次數。init、new和copy都會讓retain count加1。當銷毀對象的時候,系統不會直接調用dealloc方法,而是先調用release,讓retain count 減1,當retain count等於0的時候,系統才會調用dealloc方法來銷毀對象。

      在指標賦值的時候,retain count 是不會自動增加的,為了避免上面所說的錯誤,我們需要在賦值的時候手動retain一次,讓retain count 增加1。

      ClassA  *a = [[ClassA   alloc]  init];  // retain count = 1

      ClassA  *b = a;

      [b   retain];  // retain count = 2

      [a   dealloc];

這樣在執行到第四行的時候,對象的retain count只是減了1,並沒有被銷毀,指標b仍然有效。

 

記憶體泄露:

      就如上面列子所示,當產生ClassA對象時,指標a擁有對該對象的訪問權。如果失去了對一個對象的訪問權,而又沒有將retain count減至0,就會造成記憶體泄露。也就是說,分配出去的記憶體無法回收。

      ClassA  *a = [[ClassA   alloc]  init];

      a  =  nil;

 

三、Autorelease Pool

      為了方便程式員管理記憶體,蘋果在OC中引入了自動釋放池(Autorelease Pool)。在遵守一些規則的情況下,可以自動釋放對象。但即使有這麼一個工具,OC的記憶體仍需要程式員時刻關注(這個自動釋放池跟JAVA的記憶體回收機制不是一回事,或者說,騎馬都追不上JAVA的機制,可能連塵都吃不到)。

      ClassA  *a = [[[ClassA   alloc]  init]  autorelease];

      //retain count = 1,但無需release

Autorelease Pool 的原理:

      autorelease pool 全名叫做NSAutoreleasePool,是OC中的一個類。autorelease pool並不是天生就有的,你需要手動的去建立它

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

一般地,在建立一個iphone 項目的時候,xcode會自動地為你建立一個autorelease pool,這個pool就寫在Main函數裡面。

在NSAutoreleasePool中包含了一個 可變數組,用來儲存被聲明為autorelease的對象。當NSAutoreleasePool自身被銷毀的時候,它會遍曆這個數組,release數組中的每一個成員(注意,這裡只是release,並沒有直接銷毀對象)。若成員的retain count 大於1,那麼對象沒有被銷毀,造成記憶體泄露。

      預設的NSAutoreleasePool 只有一個,你可以在你的程式中建立NSAutoreleasePool,被標記為autorelease的對象會跟最近的NSAutoreleasePool 匹配。     NSAutoreleasePool  *pool = [[NSAutoreleasePool  alloc]  init];

      //Create some objects

      //do something…

      [pool  release];

你也可以嵌套使用NSAutoreleasePool  ,就像你嵌套使用for一樣。

      即使NSAutoreleasePool  看起來沒有手動release那麼繁瑣,但是使用NSAutoreleasePool  來管理記憶體的方法還是不推薦的。因為在一個NSAutoreleasePool  裡面,如果有大量對象被標記為autorelease,在程式啟動並執行時候,記憶體會劇增,直到NSAutoreleasePool   被銷毀的時候才會釋放。如果其中的對象足夠的多,在運行過程中你可能會收到系統的低記憶體警告,或者直接crash。

 

Autorelease Pool   擴充:

      如果你極具好奇心,把Main函數中的NSAutoreleasePool  代碼刪除掉,然後再自己的代碼中把對象聲明為autorelease,你會 發現系統並不會給你發出錯誤資訊或者警告。用記憶體偵查工具去檢測記憶體的話,你可能會驚奇的發現你的對象仍然被銷毀了。

      其實在新產生一個Run Loop的時候,系統會自動的建立一個NSAutoreleasePool  ,這個NSAutoreleasePool 無法被刪除。

 

      在做記憶體測試的時候,請不要用NSString。OC對字串作了特殊處理

      NSString  *str  =[ [NSString alloc]  stringWithString:@”123”];

在輸出str的retain count 的時候,你會發現retain count 大於1。

 

四、手動管理記憶體

      使用alloc、new、copy建立一個對象,該對象的retain count 都等於1,需要用release來釋放該對象。誰建立,誰去釋放。在這3鐘方法以外的方法建立的對象,都被系統預設的聲明為autorelease。

      ClassA  *a = [[ClassA   alloc]  init];

      ClassA  *b = a;

      [b   retain];

      //do smoething

      [b release];

      b  =  nil;

把一個指標賦值給另外一個指標的時候,a 指標所指向的對象的引用次數並沒有增加,也就是說,對象的retain count依然等於1。只有在retain了之後,retain count 才會加1。那麼,如果這時候執行[a  release],只是a指標放棄了對對象的訪問權,對象的retain count 減1,對象沒有被銷毀。只有當b也執行了release方法之後,才會將對象銷毀掉。因此,誰retain了,誰就要release。

      在對象被銷毀之後,指標依然是存在的。所以在release了之後,最好把指標賦為空白,防止無頭指標的出現。順便一說,release一個null 指標是合法的,但是不會發生任何事情。

      如果你在一個函數中建立並返回一個對象,那麼你需要把這個對象聲明為autorelease

      (ClassA  *)Function()

      {

           ClassA *a = [[[ClassA   alloc]  init]  autorelease];

           return a;

      }

不這樣做的話,會造成記憶體泄露。

 

五、屬性與記憶體管理

      蘋果一直沒有強調的一點是,關於屬性中的retain。事實上,屬性中帶有retain的,在賦值的時候可能已經在合成的setter中retain了一次,因此,這裡也需要release。

      @property實際上是getter和setter,@synthesize是合成這2個方法。為什麼在聲明了屬性之後可以用“.”來直接調用成員變數呢?那是因為聲明屬性以後系統根據你給的屬性合成了一個set方法和一個get方法。使用“.”與屬性並沒有直接關聯,如果你不嫌麻煩,在你的程式裡面多寫一個set和get方法,你也可以使用“.”來調用變數。

      @property(),如果你裡面什麼都不寫,那麼系統會預設的把你的屬性設定為:

      @property(atomic, assign)…..

關於nonatomic:

      這個屬性沒有對應的atomic關鍵字,即使我上面是這麼寫,但atomic只是在你沒有聲明這個特性的時候系統預設,你無法主動去聲明這一特性。

      如果你的程式只有一個主線程,或者你確定你的程式不會在2個或者以上線程運作的時候訪問同一個變數,那麼你可以聲明為nonatomic。指定nonatomic特性,編譯器合成訪問器的時候不會去考慮安全執行緒問題。如果你的多個線程在同一時間會訪問到這個變數的話,可以將特性聲明為atomic(通過省略關鍵字nonatomic)。在這種特性的狀態下,編輯器在合成訪問器的時候就會在訪問器裡面加一個鎖(@synchronized),在同一時間只能有一個線程訪問該變數。

      但是使用鎖是需要付出代價的,一個聲明為atomic的屬性,在設定和擷取這個變數的時候都要比聲明為nonatomic的慢。所以如果你不打算編寫多線程代碼,最好把變數的屬性特性聲明為nonatomic。

關於assign、retain和copy:

      assign是系統預設的屬性特性,它幾乎適用於OC的所有變數類型。對於非物件類型的變數,assign是唯一可選的 特性。但是如果你在引用計數下給一個物件類型的變數聲明為assign,那麼你會在編譯的時候收到一條來自編譯器的警告。因為assign對於在引用計數下的對象特性,只建立了一個弱引用(也就是平時說的淺複製)。這樣使用變數會很危險。當你release了前一個對象的時候,被賦值的對象指標就成了無頭指標了。因此在為物件類型的變數聲明屬性的時候,盡量少(或者不要)使用assign。

      關於assign合成的setter,看起來是這樣的:

      -(void)setObjA:(ClassA  *)a

      {

           objA  =  a;

      }

 

      在深入retain之前,先把聲明為retain特性的setter寫出來:

      -(void)setObjA:(ClassA  *)a

{

           If(objA != a)

{

   [objA  release];

   objA  =  a;

   [objA  retain];  //對象的retain count 加1

}

}

明顯的,在retain的setter中,變數retain了一次,那麼,即使你在程式中

      self.objA  =  a;

唯寫了這麼一句,objA仍然需要release,才能保證對象的retain count 是正確的。但是如果你的代碼

      objA  =  a;

唯寫了這麼一句,那麼這裡只是進行了一次淺複製,對象的retain count 並沒有增加,因此這樣寫的話,你不需要在後面release objA。

      這2句話的區別是,第一句使用了編譯器產生的setter來設定objA的值,而第二句只是一個簡單的指標賦值。

 

      copy的setter看起來是這樣的:

 

      -(void)setObjA:(ClassA  *)a

{

  ClassA  * temp  =  objA;

  objA  =  [a   copyWithZone:nil];

  [temp  release];

}

複製必須通過實現copyWithZone:這個方法,因次copy這個特性只適用於擁有這個方法的類型,也就是說,必須這個類支援複製。複製是把原來的對象release掉,然後讓指標指向一個新的對象的副本。因此即使在setter裡面release了原來的對象,你仍然需要在後面release新指向的對象(副本)。

 

 

六、尾聲

      IOS開發現在唯一能用的記憶體管理方式就是引用計數,無論你喜歡還是不喜歡。在一個記憶體緊缺的機器上,你編寫程式的時候也只能步步為營,儘可能的讓你的程式騰出記憶體空間,並保證系統不會給你一個警告。即使蘋果在Mac OS X 雪豹(v10.5)系統裡面添加了另外一種記憶體管理方式(垃圾收集),但目前不適用於IOS。

 

出處:http://www.cnblogs.com/ET-Union/archive/2011/08/17/2143774.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.