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抵消掉,反之亦然,否則,要麼對象不會被銷毀,要麼過早銷毀導致後面的非法引用而出錯。