Objective-C內存管理

1. 基本內存管理規則

  • 你創建的對象,你擁有它。這句話也即:你不擁有不是你創建的對象。
  • 你可以使用retain方法獲得對象的所有權。

                 這個用在訪問器方法和init**函數中。

                 

  • 如果你不再使用某對象,可以使用release或者autorelease來提示系統釋放它。
  • 你不能釋放你不擁有的對象。

 

 

實例解釋:

- (NSString *)fullName {

    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@",  self.firstName, self.lastName]; 

    return string; //內存泄露

}

 

這樣寫的代碼中string會內存泄露。因爲alloc出來的string,沒有釋放內存的地方。

 

2. 使用autorelease來讓編譯器幫你維護對象的生命週期。

- (NSString *)fullName {

NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",self.firstName, self.lastName] autorelease];

return string;

}

 

這樣寫的代碼string會返回給接收者,並且會在合適的時候釋放string內存。

 

- (NSString *)fullName {

NSString *string = [NSString stringWithFormat:@"%@ %@",self.firstName, self.lastName];

return string;

}

這段代碼中stringWithFormat會創建NSString,所以string本身不擁有內存怒,所以這樣返回不會再本函數內造成內存泄露。

(猜測:stringWithFormat內部的實現在返回string的時候已經把這個值做了autorelease處理。)

 

 

3. 你不擁有返回引用的對象。You don't Own Objects returned by reference

就是說參數類型是ClassName**或者id*類型的對象。

  NSString *fileName = <#Get a file name#>;

  NSError *error;

  NSString *string = [[NSString alloc] initWithContentsOfFile:fileName

  encoding:NSUTF8StringEncoding error:&error];

  if (string == nil) {

     // Deal with error...

  }

  // ...

  [string release];

 

解釋一下:這裏error是考察的重點。可見的代碼中實際上沒有對error對象做任何內存申請。猜測這個工作是由函數initWithContentsOfFile:encoding:error: (NSString)做的。所以你不需要對這個對象做release。同上的道理,說不定這個地方又是在內部做了autorelease。

 

 

4. 自定義類中dealloc的實現

自定義類在構造函數內部爲成員變量申請了內存的。就需要實現dealloc函數來釋放這部分內存。

 

- (void)dealloc {

   [_firstName release];

   [_lastName release];

   [super dealloc];

}

 

5. Core Foundation中的內存管理

在Core Foundation中,創建的對象你不能釋放。、

MyClass *myInstance = [MyClass createInstance];

 

 

下面的討論假設一個類:

@interface Counter : NSObject

 

@property (nonatomic, retain) NSNumber *count;

 

@end;

 

6. 實踐篇 之  使用訪問方法管理內存

下面是一個getter的寫法:

- (NSNumber *)count {

  return _count;

}

這個沒有什麼特別的地方,直接返回。既不創建,也不銷燬。不生不滅!

 

下面是setter的寫法:

- (void)setCount:(NSNumber *)newCount {

  [newCount retain];

  [_count release];

  // Make the new assignment.

  _count = newCount;

}

這裏,賦值的時候是假定newCount是要把擁有權交給setter函數的,這裏應該先把newCount的擁有權拿過來,然後再清空原本的_count,再給_count賦值。

OC中向nil發送消息不會崩潰,只是沒有任何反應!

 

網上還有一種寫法:

- (void)setCount:(NSNumber *)newCount {

  if (newCount != _count){

    _count = [newCount retain];

  }

}

 

爲了說明情況,這裏列舉一種錯誤的做法:

- (void)setCount:(NSNumber *)newCount {

  [_count release];

  // Make the new assignment.

  _count = newCount;

}

之所以說這個是錯誤的,是因爲,如果外部調用的時候不小心把原本指向_count的引用傳入了,結果第一句[_count release];就把這塊兒內存釋放了,所以造成了錯誤。

 

 

7. 實踐篇 之 使用訪問方法設置屬性

- (void)reset {

    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

    [self setCount:zero];

    [zero release];

}

創建一個臨時變量,設置到類中,銷燬臨時變量。

- (void)reset {

NSNumber *zero = [NSNumber numberWithInteger:0];

[self setCount:zero];

}

實際上還是上面的那個版本,只不過創建和銷燬由一個NSNumber類函數完成而已。

 

- (void)reset {

  NSNumber *zero = [[NSNumber alloc] initWithInteger:0];

  [_count release];

  _count = zero;

}

這個沒有使用訪問方法,這是一種“重起爐竈”的做法。

 

8. 實踐篇 之 初始化方法

曰:唯一不能使用訪問方法的地方就是初始化函數和dealloc。

- init {

  self = [super init];

  if (self) {

    _count = [[NSNumber alloc] initWithInteger:0];

  }

  return self;

}

這是初始化_count爲0的做法。

 

- initWithCount:(NSNumber *)startingCount {

  self = [super init];

  if (self) {

    _count = [startingCount copy];

  }

  return self;

}

這是構造函數的實現方法。

 

- (void)dealloc {

  [_count release];

  [super dealloc];

}

這是釋放內存的方法。

 

9. 循環引用的正確做法:strong reference + weak reference

 

在上面的圖中,是一種正確的變量引用的方法:Document擁有一個Page的強指針,Page擁有一個Paragraph的強指針;同時,Page擁有一個指向其包含對象的一個弱變量,Paragraph擁有一個包含對象的Page引用弱變量。弱引用是一種無擁有權的引用,也即這種類型的應用變量不擁有指向的對象內存的擁有權(實際上有擁有權可以理解爲就是增加引用計數的動作啦,放棄擁有權可以理解爲減少引用計數的動作)!

如果相互循環引用的兩個變量都是強變量的話, 那麼就會出現無法釋放內存的情況。因爲系統看到的是兩個對象都還有擁有者,而系統是不會釋放有擁有者的變量的內存的 。

當你向一個弱變量對象發送消息的時候要注意做好判斷,因爲如果這個變量的內存已經被釋放了,這種情況下向該對象發送消息是會造成程序崩潰的。如此,你需要判斷好對象何時是有效的。需要做的就是在某對象釋放前通知這個弱引用變量,令其做好“準備”!

文檔上給出一個消息接收對象和消息中心的例子(觀察者模式?)是:

For example, when you register an object with a notification center , the notification center stores  a weak reference to the object and sends messages to it when the appropriate notifications are posted. When the object is deallocated, you need to unregister it with the notification center to prevent the notification center from sending any further messages to the object, which no longer exists. 

//added @ 2015.2.26

強引用是要影響其指向對象的擁有者個數的,而弱引用不影響所指對象的引用計數。

 

 

10. 避免釋放你在使用的對象

Cocoa的擁有權策略是:在調用對象方法的作用域中,對象應該是有效的,即不能釋放內存。但是,有兩個例外:

一是:一個foundation集合類中的對象被刪除的時候

heisenObject = [array objectAtIndex:n];   

[array removeObjectAtIndex:n];   

// heisenObject could now be invalid.   

二是:父對象被刪除了,於是其“子”對象也被父對象的dealloc刪除了。

id parent = <#create a parent object#>;

// ...

heisenObject = [parent child] ;

[parent release]; // Or, for example: self.parent = nil;

// heisenObject could now be invalid.

爲此,如果你想要保證這類對象在調用作用域中的有效,那麼你需要使用retain

heisenObject = [[array objectAtIndex:n] retain];  //取出來的時候就給它的引用計數加1

[array removeObjectAtIndex:n];

// Use heisenObject...

[heisenObject release];

 

11. 不要在dealloc中管理稀缺資源

應該的做法是:在確定不再使用該資源的時候,用明確的消息來處理稀缺資源的釋放,然後再調用release釋放實例。

//此處省略300字。文檔上這段講的看不大懂。

 

12. 集合擁有其所包含對象的所有權

這裏的集合包括array、dictionary、set之類。當你把元素存入集合之後集合將會得到這個元素的擁有權。所以添加元素到集合中,你可以這樣做:

方法1:

NSMutableArray *array = <#Get a mutable array#>;

NSUInteger i;

// ...

for (i = 0; i < 10; i++) {

NSNumber *convenienceNumber = [NSNumber numberWithInteger:i];

[array addObject:convenienceNumber];

}

沒有顯式地使用alloc,所以也不需要release。添加convenienceNumber到array的時候array會得到convenienceNumber的擁有權。

 

方法2:

NSMutableArray *array = <#Get a mutable array#>;

NSUInteger i;

// ...

for (i = 0; i < 10; i++) {

  NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];

  [array addObject:allocedNumber];

  [allocedNumber release];

}

這裏例子裏,alloc的時候對象allocedNnumber的引用計數爲1,向array消息addObject:,內部會retain一下以獲得擁有權,這樣引用計數變爲2,最後一句release使得allocedNumber的引用計數變爲1.這樣array最終擁有了allocedNumber的所有權。

 

13. 使用引用計數來實現所有權策略    (Σ( ° △ °|||)︴,到現在纔講!)

引用計數的變化規則是這樣的:

  • 創建對象的時候引用計數爲1
  • 給對象發送retain消息,引用計數加1
  • 給對象發送release消息,引用計數減1
  • 給對象發送autorelease消息,在當前自動釋放池塊結束的時候,對象的引用計數才減1
  • 如果對象的引用計數減到0,那麼會被釋放內存(dealloc)

 

14. 使用autorelease塊

@autoreleasepool {

  // Code that creates autoreleased objects.

}

autorelease塊的結尾處,會給塊中的每個收到autorelease消息的對象發送release。

autorelease塊也可以嵌套使用。

@autoreleasepool {

  // . . .

  @autoreleasepool {

    // . . .

  }

  . . .

}

一般使用autorelease塊來管理內存,不需要手動寫代碼釋放內存。但是有一些例外的情況,你就需要自己寫autorelease pool塊了:

  • 你寫的代碼不是基於UI框架的
  • 你寫的循環申請了許多臨時對象。爲了節省內存、需要及時釋放內存,所以需要再循環內部使用autorelease塊。
  • 一旦你開啓另外一個線程,你就必須創建一個你自己的autorelease pool塊。

 

15. 使用局部自動釋放池塊來減少內存使用峯值

一般情況下,使用autorelease來釋放臨時對象沒有問題,但是如果你要創建的臨時對象有很多,這樣會導致內存使用激增的話,還是要使用局部自動釋放池塊來控制對象的釋放。

NSArray *urls = <# An array of file URLs #>;

for (NSURL *url in urls) {

  @autoreleasepool {

    NSError *error;

    NSString *fileContents = [NSString stringWithContentsOfURL:url

    encoding:NSUTF8StringEncoding error:&error];

    /* Process the string, creating and autoreleasing more objects. */

  }

}

 

如果需要使用臨時變量橫跨自動釋放池,可以在塊內給對象發送retain消息,並在autorelease塊之後時候給對象發送autorelease消息。

– (id)findMatchingObject:(id)anObject {

  id match;

  while (match == nil) {

    @autoreleasepool {

      /* Do a search that creates a lot of temporary objects. */

      match = [self expensiveSearchForObject:anObject];

      if (match != nil) {

        [match retain]; /* Keep match around. */

      }

    }

  }

  return [match autorelease]; /* Let match go and return it. */

}

解釋一下:這裏例子中,我們想要在self的某個集合中找到滿足條件anObject的一個元素,如果找到的話,就用match返回。在@autorelease內部,如果找到了match對象,那麼要retain一下,以保證可以返回!如果沒找到,那麼match對象會在跳出autorelease塊時釋放內存。而最後一句match的autorelease會保證match在合適的時機讓自動釋放池通知來釋放內存。

 

16. 自動釋放池塊和線程

Cocoa程序中的每一個線程都有一個自己的自動釋放池塊的棧。如果你寫的是一個foundation-only的程序,或者你要分離一個線程,那麼你需要創建你自己的自動釋放池塊。

此處又是言簡意賅了!留待以後補充

 

 

 

 

最後說一句:這麼斷章取義地學習內存管理,真他媽的害死人啊!!!

應該的做法是,直接上實例進行解釋啊!這個蘋果的文檔寫的真“文檔”啊,一點兒也不“教程”!

更何況,到現在爲止Objective-C的內存管理還發生了變化,之前“手動模式”,之後(XCode4.2)使用ARC(auto reference count),文檔居然不說明一下!真當是別人都很“蘋果”啊!

 

 

最後,這個圖 真好!:

 

 

 

 

Doc

[D.1]  使用Xcode和Instruments調試解決iOS內存泄露

[D.2]  蘋果官方:高級內存管理指南 Advanced Memory Management Programming Guide

 

 

 

     

 

 

 

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