IOS開發(40)之objective-C 的記憶體管理之-引用計數

來源:互聯網
上載者:User

obj-c本質就是"改進過的c語言",大家都知道c語言是沒有記憶體回收(GC)機制的(註:雖然obj-c2.0後來增加了GC功能,但是在iphone上不能用,因此對於iOS平台的程式員來講,這個幾乎沒啥用),所以在obj-c中寫程式時,對於資源的釋放得由開發人員手動處理,相對要費心一些。

引用計數

這是一種古老但有效記憶體管理方式。每個對象(特指:類的執行個體)內部都有一個retainCount的引用計數,對象剛被建立時,retainCount為1,可以手動調用retain方法使retainCount+1,同樣也可以手動調用release方法使retainCount-1,調用release方法時,如果retainCount值減到0,系統將自動調用對象的dealloc方法(類似於c#中的dispose方法),開發人員可以在dealloc中釋放或清理資源。

1、基本用法

為了示範這種基本方式,先定義一個類Sample

類介面部分Sample.h

//
//  Sample.h
//  MemoryManage_1
//
//  Created by jimmy.yang on 11-2-19.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>


@interface Sample : NSObject {

}

@end
類實現部分Sample.m

//
//  Sample.m
//  MemoryManage_1
//
//  Created by jimmy.yang on 11-2-19.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "Sample.h"


@implementation Sample

-(id) init
{
 if (self=[super init]){
  NSLog(@"建構函式被調用了!當前引用計數:%d",[self retainCount]);
 }
 return (self);
}

-(void) dealloc{
 NSLog(@"解構函式將要執行...,當前引用計數:%d",[self retainCount]);
 [super dealloc];
}
@end
代碼很簡單,除了"建構函式"跟"解構函式"之外,沒有任何其它多餘處理。

主程式調用

#import <Foundation/Foundation.h>
#import "Sample.h"

int main (int argc, const char * argv[]) { 
 
 Sample *_sample = [Sample new]; //建構函式被調用了!當前引用計數:1
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//1 
 
 [_sample retain];
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//2
 
 [_sample retain];
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//3 
 
 [_sample release];
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//2
 
 [_sample release];
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//1
 
 [_sample release];//解構函式將要執行...,當前引用計數:1
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//1,註:即便是在解構函式執行後,如果立即再次引用對象的retainCount,仍然返回1,但以後不管再試圖引用該對象的任何屬性或方法,都將報錯
 NSLog(@"_sample.retainCount=%d",[_sample retainCount]);//對象被釋放之後,如果再嘗試引用該對象的任何其它方法,則報錯
 //[_sample retain];//同上,會報錯 
 
 return 0; 
}
這段代碼主要驗證:對象剛建立時retainCount是否為1,以及retain和release是否可以改變retainCount的值,同時retainCount減到0時,是否會自動執行dealloc函數

nil 的問題:

1.1 如果僅聲明一個Sample類型的變數(其實就是一個指標),而不執行個體化,其初始值為nil

1.2 變數執行個體化以後,就算release掉,dealloc被成功調用,其retainCount並不馬上回到0(還能立即調用一次且僅一次[xxx retainCount]),而且指標變數本身也不會自動歸為nil值

1.3 dealloc被調用後,必須手動賦值nil,retainCount才會自動歸0

以上結論是實際實驗得出來的,見下面的代碼:

 Sample *s ; 
 NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is nil,retainCount=0 
 s = [Sample new];
 NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is not nil,retainCount=1 
 [s release];
 NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is not nil,retainCount=1
 //NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//報錯:Program received signal:  “EXC_BAD_ACCESS”.
 s = nil;
 NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]);//s is nil,retainCount=0
所以千萬別用if (x == nil) 或 if ([x retainCount]==0)來判斷對象是否被銷毀,除非你每次銷毀對象後,手動顯式將其賦值為nil

2、複雜情況

上面的樣本過於簡章,只有一個類自己獨耍,如果有多個類,且相互之間有聯絡時,情況要複雜一些。下面我們設計二個類Shoe和Man(即“鞋子類”和”人“),每個人都要穿鞋,所以Man與Shoe之間應該是Man擁有Shoe的關係。

Shoe.h介面定義部分

#import <Foundation/Foundation.h>


@interface Shoe : NSObject {
 NSString* _shoeColor;
 int _shoeSize;
}

//鞋子尺寸
-(void) setSize:(int) size;
-(int) Size;

//鞋子顏色
-(void) setColor:(NSString*) color;
-(NSString*) Color;

//設定鞋子的顏色和尺碼
-(void) setColorAndSize:(NSString*) pColor shoeSize:(int) pSize;

@end
Shoe.m實現部分

//
//  Shoe.m
//  MemoryManage_1
//
//  Created by jimmy.yang on 11-2-19.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "Shoe.h"


@implementation Shoe

//建構函式
-(id)init
{
 if (self=[super init]){
  _shoeColor = @"black";
  _shoeSize = 35;
 }
 NSLog(@"一雙 %@ %d碼 的鞋子造好了!",_shoeColor,_shoeSize);
 return (self);
}

-(void) setColor:(NSString *) newColor
{
 _shoeColor = newColor;
}

-(NSString*) Color
{
 return _shoeColor;
}

-(void) setSize:(int) newSize
{
 _shoeSize = newSize;

-(int) Size
{
 return _shoeSize;
}

-(void) setColorAndSize:(NSString *)color shoeSize:(int)size
{
 [self setColor:color];
 [self setSize:size];
}


//解構函式
-(void) dealloc
{
 NSLog(@"%@ %d碼的鞋子正在被人道毀滅!",_shoeColor,_shoeSize);
 [super dealloc];
}


@end
Man.h定義部分

//
//  Man.h
//  MemoryManage_1
//
//  Created by jimmy.yang on 11-2-20.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Shoe.h"


@interface Man : NSObject {
 NSString *_name;
 Shoe *_shoe;
}


-(void) setName:(NSString*) name;
-(NSString*)Name;

-(void) wearShoe:(Shoe*) shoe;
@end
Man.m實現部分

//
//  Man.m
//  MemoryManage_1
//
//  Created by jimmy.yang on 11-2-20.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "Man.h"


@implementation Man

//建構函式
-(id)init
{
 if (self=[super init]){
  _name = @"no name";
 }
 NSLog(@"新人\"%@\"出生了!",_name);
 return (self);
}


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

-(NSString*)Name
{
 return _name;
}

-(void) wearShoe:(Shoe *)shoe
{
 _shoe = shoe;
}

//解構函式
-(void) dealloc
{
 NSLog(@"\"%@\"要死了! ",_name);
 [super dealloc];
}
 
@end
main函數調用

#import <Foundation/Foundation.h>
#import "Shoe.h"
#import "Man.h"

int main (int argc, const char * argv[]) { 
 
 Man *jimmy = [Man new];
 [jimmy setName:@"Jimmy"];
 
 
 Shoe *black40 =[Shoe new];
 [black40 setColorAndSize:@"Black" shoeSize:40];
 
 [jimmy wearShoe:black40];
 
 [jimmy release];
 [black40 release];
 
 return 0;
   
}
2011-02-23 13:05:50.550 MemoryManage[253:a0f] 新人"no name"出生了!
2011-02-23 13:05:50.560 MemoryManage[253:a0f] 一雙 black 35碼 的鞋子造好了!
2011-02-23 13:05:50.634 MemoryManage[253:a0f] "Jimmy"要死了!
2011-02-23 13:05:50.636 MemoryManage[253:a0f] Black 40碼的鞋子正在被人道毀滅!

以上是輸出結果,一切正常,jimmy與black40佔用的資源最終都得到了釋放。但是有一點不太合理,既然鞋子(black40)是屬於人(jimmy)的,為什麼人死了(即:[jimmy release]),卻還要main函數來責任燒掉他的鞋子?(即:main函數中還是單獨寫一行[black40 release]) 貌似人死的時候,就連帶自上的所有東西一併帶走,這樣更方便吧。

ok,我們來改造一下Man.m中的dealloc()方法,改成下面這樣:

//解構函式
-(void) dealloc
{
 NSLog(@"\"%@\"要死了! ",_name);
 [_shoe release];//這裡釋放_shoe
 [super dealloc];
}
即:在Man被銷毀的時候,先把_shoe給銷毀。這樣在main()函數中,就不再需要單獨寫一行[black40 release]來釋放black40了.

現在又有新情況了:jimmy交了一個好朋友mike,二人成了鐵哥們,然後jimmy決定把自己的鞋子black40,跟mike共同擁有,於是main函數就成了下面這樣:

int main (int argc, const char * argv[]) { 
 
 Man *jimmy = [Man new];
 [jimmy setName:@"Jimmy"]; 
 
 Shoe *black40 =[Shoe new];
 [black40 setColorAndSize:@"Black" shoeSize:40];
 
 [jimmy wearShoe:black40];
 
 Man *mike = [Man new];
 [mike setName:@"mike"];
 [mike wearShoe:black40];//mike跟jimmy,現在共同擁有一雙40碼黑色的鞋子
 
 [jimmy release];
 [mike release]; 
 
    return 0;
}
麻煩來了:jimmy在掛掉的時候(即[jimmy release]這一行),已經順手把自己的鞋子也給銷毀了(也許他忘記了mike也在穿它),然後mike在死的時候,準備燒掉自已的鞋子black40,卻被告之該對象已經不存在了。於是程式運行報錯:

Running…
2011-02-23 13:38:53.169 MemoryManage[374:a0f] 新人"no name"出生了!
2011-02-23 13:38:53.176 MemoryManage[374:a0f] 一雙 black 35碼 的鞋子造好了!
2011-02-23 13:38:53.177 MemoryManage[374:a0f] 新人"no name"出生了!
2011-02-23 13:38:53.179 MemoryManage[374:a0f] "Jimmy"要死了!
2011-02-23 13:38:53.181 MemoryManage[374:a0f] Black 40碼的鞋子正在被人道毀滅!
2011-02-23 13:38:53.183 MemoryManage[374:a0f] "mike"要死了!
Program received signal:  “EXC_BAD_ACCESS”.
sharedlibrary apply-load-rules all
(gdb)

上面紅色的部分表示程式出錯了:Bad_Access也就是說訪問不存在的地址。

最解決的辦法莫過於又回到原點,Man.m的dealloc中不連帶釋放Shoe執行個體,然後把共用的鞋子放到main函數中,等所有人都掛掉後,最後再銷毀Shoe執行個體,但是估計main()函數會有意見了:你們二個都死了,還要來麻煩我料理後事。

舉這個例子無非就是得出這樣一個原則:對於new出來的對象,使用retain造成的影響一定要運用相應的release抵消掉,反之亦然,否則,要麼對象不會被銷毀,要麼過早銷毀導致後面的非法引用而出錯。

 

相關文章

聯繫我們

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