Objective-C的MRC手動內存管理——引用計數詳解

0x01 對象生命週期

對象的生命週期包括誕生(通過alloc或new方法實現)、生存(接收消息並執行操作)、交友(通過複合以及向方法傳遞參數)以及最終死去(被釋放)。

生命週期結束後,它們所佔用的內存將被回收並被新的對象使用。

 

0x02 基本的內存分配規則

Objective-C的對象都是在堆裏面生成,然後有一個指針指向它,使用完後不會自動銷燬,需要執行dealloc來銷燬,否則就會出現內存泄漏:

ClassA *obj1 = [[ClassA alloc] init];   //alloc
[obj1 dealloc];                         //dealloc

 

0x03 引用計數

Cocoa採用了引用計數(Reference Counting)或稱爲保留計數(Retain Counting)的技術來表示對象的生命。

每個對象都有一個與之相關聯的整數作爲引用計數器。

當某段代碼需要訪問一個對象時,該代碼就將該對象的引用計數器值+1,表示“我需要訪問該對象”;

當某段代碼結束對象訪問時,該代碼就將該對象的引用計數器值-1,表示“不再訪問該對象”;

當對象的引用計數器值爲0時,表示“不再有代碼訪問該對象了”,因此該對象將被銷燬,其佔用的內存資源將被釋放。

 

獲得引用計數器的值

可以發送retainCount消息來獲得引用計數器的值:

- (NSUInteger) retainCount;

當使用alloc、new方法或者通過copy消息(接收到消息的對象會創建一個自身的副本)創建一個對象時,對象的引用計數器被置爲1。

要增加對象引用計數器的值,可以給對象發送一條retain消息,retain消息返回的是一個id類型的值:

- (id) retain;

要減少對象的引用計數器值,可以給對象發送一條release消息:

- (oneway void) release;

執行release方法釋放的是一塊內存區域的內容,而不是僅僅切斷對象和該內存區域的聯繫!

 

示例程序:

@interface RetainTracker : NSObject
@end // RetainTracker

@implementation RetainTracker
- (id) init
{
  if (self = [super init]) {
       [self retainCount]);
}
NSLog (@"init: Retain count of %d.",  
        return (self);
} // init

- (void) dealloc
{
  NSLog (@"dealloc called. Bye Bye.");
  [super dealloc];
} // dealloc
@end // RetainTracker

int main (int argc, const char *argv[])
{
RetainTracker *tracker = [RetainTracker new];  // count: 1
[tracker retain];                              // count: 2
NSLog (@"%d", [tracker retainCount]);
[tracker retain];                              // count: 3
NSLog (@"%d", [tracker retainCount]);
[tracker release];                             // count: 2
NSLog (@"%d", [tracker retainCount]);
[tracker release];                             // count: 1
NSLog (@"%d", [tracker retainCount]);
[tracker retain];                              // count 2
NSLog (@"%d", [tracker retainCount]);
[tracker release];                             // count 1
NSLog (@"%d", [tracker retainCount]);
[tracker release];                             // count: 0, dealloc it
return (0);
} // main

當一個對象因其保留計數器歸0而即將被銷燬時,Objective-C會自動向對象發送一條dealloc消息。

我們可以自己在對象中重寫dealloc方法,這樣就能釋放掉已經分配的全部相關資源。

但一定不要直接調用dealloc方法,因爲Objective-C會在需要銷燬對象的時候自己調用它!

 

0x04 對象所有權

如果一個對象內有指向其他對象的實例變量,則稱該對象擁有這些對象。

一般來說,我們說某個實體“擁有一個對象”時,其實也意味着該實體要負責清理其擁有的對象所佔用的資源。

當多個實體擁有某個特定對象時,對象的所有權將變得很複雜,這也是引用計數器的值大於1的原因。

所有權變得複雜起來,就可能出現下列情況:

ClassA *obj1 = [[ClassA alloc] init];      // count = 1
[obj1 release];                            // count = 0, 自動dealloc
ClassA *obj1 = [[ClassA alloc] init];      // count = 1
ClassA *obj2 = obj1;                       // count = 1, 指針賦值不會增加引用計數
[obj1 hello];
[obj1 release];                            // count = 0, 自動dealloc
// 因對象已被dealloc, 故以下代碼不會執行, obj2仍然是無效指針:
[obj2 hello];
[obj2 release];

正確的做法是,凡是訪問方法,都先保留新對象,再釋放對象:

ClassA *obj1 = [[ClassA alloc] init];     // count = 1
ClassA *obj2 = obj1;                      // count = 1
[obj2 retain];                            // count = 2,先爲新對象做保留
[obj1 hello];
[obj1 release];                           // count = 1
[obj2 hello];
[obj2 release];                           // count = 0, 自動dealloc

按照這個規則,我們可以編寫setEngine方法的內存管理版本:

- (void) setEngine: (Engine *) newEngine
{
 [newEngine retain];                  //保留新對象
 [engine release];                    //釋放對象
 engine = newEngine;
} // setEngine

 

 

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