Objective-C 內存管理

本文記錄Objective-C在內存管理方面的一些注意點。另有一篇轉載的未公開筆記——Objective-C內存管理機制學習筆記【轉】html

引用計數

在引用計數中,每個對象負責維護對象全部引用的計數值。編程

對象的初始引用計數值爲1。若是引用計數的值爲0,則對象就會自動dealloc。ide

包含alloc/new/copy/mutableCopy的方法 引用計數+1函數

retain 引用計數+1學習

release 引用計數-1ui

在 Objective-C 有2種管理內存的方法, 1) retain and release or 2) retain and release/autorelease。url

對於每一個 retain,必定要對應一個 release 或一個 autorelease。spa

查看方法:[object retainCount];指針

生成並持有對象 alloc/new/copy/mutableCopy等方法
持有對象 retain方法
釋放對象 release方法
廢棄對象 deallco方法

 

 

 

 

我的理解,所謂持有對象,能夠簡單理解爲負責該對象的釋放。code

 

內存管理的思考方式

  • 本身生成的對象,本身持有
  • 非本身生成的對象,本身也能持有
  • 本身持有的對象須要本身釋放
  • 非本身持有的對象沒法釋放

 

添加對象到Array時記得release

1. obj對象建立後, 計數爲1
2. obj對象加入到array中後,計數+1,爲2
3. obj對象從array中移除後,計數-1,爲1

Object *obj = [[Object alloc] init];   //1
[array addObject:obj];                 //2
[array removeObjectAtIndex:0];         //1
/*此時obj的引用計數爲1,內存泄漏*/

addObject和removeObjectAtIndex是一對,由系統管理引用計數。而咱們輸入的Object *obj = [[Object alloc] init];並無一個release與之對應,因此形成obj沒有被正確釋放。

解決方法是在obj對象添加到array後,release它。

Object *obj = [[Object alloc] init];   //1
[array addObject:obj];                 //2
[obj release];               //1
[array removeObjectAtIndex:0];         //0
/*此時obj的引用計數爲0,內存不泄漏*/

 

NSArray

NSArray* immutableArray = [[NSArray alloc] initWithArray:mutableArray]
NSArray* immutableArray = [NSArray arrayWithArray:mutableArray]; 
NSArray* immutableArray = [mutableArray copy];

 

1. alloc和copy都會分配內存,須要手動release。因此調用第一個和第三個都須要 [immutableArray release].

2. arrayWithArray也會分配內存,不過系統會來管理這塊內存,不須要手動release。若是想要本身管理,能夠這樣:

NSArray* immutableArray = [[NSArray arrayWithArray:mutableArray] retain];

[immutableArray release];

 

 

dealloc負責本類屬性的釋放及調用父類的dealloc

 當類中包含其餘指針,就要在dealloc函數中手動一一release它們,最後記得[super dealloc]。

 

讓函數返回一個autorelease對象

- (NSString *)f
{
    NSString *result = [[NSString alloc] initWithFormat:@"Hello"];
    return result;
}

這樣作實際上是會內存泄漏的。alloc方法會建立出來一個string對象,它的retain計數爲1。所以該string對象返回時,retain計數爲1。在其餘對象調用f方法獲得string對象後,它通常會retain該string對象(由於調用者認爲f返回的應該是一個autorelease對象)。這時,string對象的retain計數變成2。而後調用者在再也不須要stirng對象時,他將會調用release(由於他retain了一次,因此會release一次)。這時string對象的retain計數變成1。正如你所想, string對象沒有獲得釋放。

錯誤的解決方法:讓函數返回前使用[result release];

這樣返回的函數對象其實已是空的了。不可行。

正確的解決方法:讓函數返回一個autorelease對象。

- (NSString *)f
{
    NSString *result = [[NSString alloc] initWithFormat:@"Hello"];
    return [result autorelease];
}

 

Autorelease

Autorelease實際上只是把對release的調用延遲了,對於每個Autorelease,系統只是把該Object放入了當前的Autorelease pool中,當該pool被釋放時,該pool中的全部Object會被調用Release。 
autorelease和release沒什麼區別,只是引用計數減一的時機不一樣而已,autorelease會在對象的使用真正結束的時候才作引用計數減一。 

函數返回的是一個autorelease對象,而接到它的對象通常須要retain,而後有retain就須要咱們手動release。

若是有AutoreleasePool,那麼在內存池管理的範圍內的autorelease都不須要咱們手動釋放。

  • NSString *str1 = @"constant string";常量字符串已經自動加入autorelease pool,不須要本身管理內存了。
  • [NSString stringWithString: @"String"] 這種 method 使用了 autorelease pool,不須要本身管理內存了。
  • alloc method 如 [[NSString alloc] initWithString: @"String"] 則沒有使用 auto release pool,須要本身release。

自動釋放池對象一般以以下模式建立:

[[[Object alloc] init] autorelease];

 

NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
A *a = [[A alloc] init];    //引用計數爲1
[pool drain];                //引用計數依然爲1
[a retain];                    //引用計數爲2
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
[a autorelease];            //將a添加到pool中,當pool釋放的時候,a也被釋放
[pool drain];                //引用計數爲1
[a release];                //引用計數爲0
  • 消息autorelease的做用是將對象添加到自動釋放池中,當池被釋放的時候,系統將向池中的對象發送一條release的消息。
  • 消息autorelease並不會影響對象的引用計數。

autorelease可能致使存在大量臨時對象

- (void)f
{
  for(int i = 0; i < 100000; ++i)
  {
    //getData返回一個autorelease對象
    NSData *data = [self getData];
  }
  //在這裏100000個數據對象都還有效
}

因此,autorelease可能致使存在大量臨時對象。

解決方法1:在循環中釋放對象

- (void)f
{
  for(int i = 0; i < 100000; ++i)
  {
    NSData
*data = [[NSData alloc]init]; /* * set data, use data */
    [data release];   }   //在這裏100000個數據對象都被成功釋放 }

解決方法2:循環內部建立一個自動釋放池

- (void)f
{
  for(int i = 0; i < 100000; ++i)
  {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

    //getData返回一個autorelease對象
    NSData *data = [self getData];

    [pool drain];
  }
  //在這裏100000個數據對象都被成功釋放
}

 

setter中的內存管理

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

這樣寫有什麼不對的地方呢,當newName在某個地方被release後,該name將失效!
改進後的寫法應該以下,以防其餘人釋放name引用的對象而致使name失效。

- (void)setName:(NSString *)newName
{
    name = newName;
    [name retain];
}

這樣寫也有問題,當第二次調用setName的時候,原來的name佔的空間並無釋放;並且retain以後何時release這個對象?
改進後的寫法應該以下

- (void)setName:(NSString *)newName
{
    [name release];    //釋放舊值
    name = [newName retain];
}

- (void)dealloc
{
    [name release];    //對應setName中的retain
    [super dealloc];
}

但是但是但是,這樣仍是有問題,假如本身傳值給本身的時候會怎樣呢?因此,最正確的應該是

- (void)setName:(NSString *)newName
{
    [newName retain];  //注意,順序必定是先retain再release。
    [name release];
    name = newName;
}

- (void)dealloc
{
    [name release];    //對應setName中的retain
    [super dealloc];
}

注意,順序必定是先retain再release。固然還有其餘寫法,詳見《Cocoa® Programming for Mac® OS X》中的內存管理章節,不過我的比較推崇這種寫法。

 

最後的問題是,當newName改變的時候,name也會跟着改變,由於這是淺複製。若是想要讓兩者獨立的話,即深複製,應該這樣寫

- (void)setName:(NSString *)newName
{
    if (name != newName)  //防止複製自身
    {
        [name release];
        name = [[NSString alloc] initWithString:newName];
    }
}
- (void)dealloc
{
    [name release];
    [super dealloc];
}

 

其餘

1.

NSNumber *myInt = [NSNumber numberWithInteger:100];    //引用計數爲1

2.

myInt = [myArr objectAtIndex:0];
[myArr removeObjectAtIndex:0];

此時,myInt引用的對象失效。應當修改成:

myInt = [myArr objectAtIndex:0];
[myInt retain];
[myArr removeObjectAtIndex:0];

3.

NSString *s1 = @"s1";    //引用計數爲0xffffffff(不少f就對了)
NSString *s2 = [NSString stringWithString:@"s2"];    //引用計數爲0xffffffff
NSMutableString *s3 = [NSMutableString stringWithString:@"s3"];    //引用計數爲1

爲何呢,由於s1是常量字符串,s2是使用了常量字符串初始化的不可變字符串對象,都沒有引用計數機制。

 

參考文獻

Objective-C Beginner's Guide

Cocoa® Programming for Mac® OS X》中的內存管理章節

Objective-C高級編程》中的自動引用計數部分

objc內存管理

相關文章
相關標籤/搜索