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
1 //
2 // Sample.h
3 // MemoryManage_1
4 //
5 // Created by jimmy.yang on 11-2-19.
6 // Copyright 2011 __MyCompanyName__. All rights reserved.
7 //
8
9 #import < Foundation / Foundation.h >
10
11 @interface Sample : NSObject {
12
13 }
14 @end
类实现部分Sample.m
1 //
2 // Sample.m
3 // MemoryManage_1
4 //
5 // Created by jimmy.yang on 11-2-19.
6 // Copyright 2011 __MyCompanyName__. All rights reserved.
7 //
8
9 #import " Sample.h "
10
11 @implementation Sample
12
13 - (id) init
14 {
15 if (self = [super init]){
16 NSLog( @" 构造函数被调用了!当前引用计数:%d " ,[self retainCount]);
17 }
18 return (self);
19 }
20
21 - ( void ) dealloc{
22 NSLog( @" 析构函数将要执行...,当前引用计数:%d " ,[self retainCount]);
23 [super dealloc];
24 }
25 @end
代码很简单,除了"构造函数"跟"析构函数"之外,没有任何其它多余处理。
主程序调用
1 #import < Foundation / Foundation.h >
2 #import " Sample.h "
3
4 int main ( int argc, const char * argv[]) {
5
6 Sample * _sample = [Sample new ]; // 构造函数被调用了!当前引用计数:1
7 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 1
8
9 [_sample retain];
10 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 2
11
12 [_sample retain];
13 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 3
14
15 [_sample release];
16 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 2
17
18 [_sample release];
19 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 1
20
21 [_sample release]; // 析构函数将要执行...,当前引用计数:1
22 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 1,注:即便是在析构函数执行后,如果立即再次引用对象的retainCount,仍然返回1,但以后不管再试图引用该对象的任何属性或方法,都将报错
23 NSLog( @" _sample.retainCount=%d " ,[_sample retainCount]); // 对象被释放之后,如果再尝试引用该对象的任何其它方法,则报错
24 // [_sample retain]; // 同上,会报错
25
26 return 0 ;
27 }
这段代码主要验证:对象刚创建时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
以上结论是实际试验得出来的,见下面的代码:
1 Sample * s ;
2 NSLog( @" s %@,retainCount=%d " ,s == nil ? @" is nil " : @" is not nil " ,[s retainCount]); // s is nil,retainCount=0
3 s = [Sample new ];
4 NSLog( @" s %@,retainCount=%d " ,s == nil ? @" is nil " : @" is not nil " ,[s retainCount]); // s is not nil,retainCount=1
5 [s release];
6 NSLog( @" s %@,retainCount=%d " ,s == nil ? @" is nil " : @" is not nil " ,[s retainCount]); // s is not nil,retainCount=1
7 // NSLog(@"s %@,retainCount=%d",s==nil?@"is nil":@"is not nil",[s retainCount]); // 报错:Program received signal: “EXC_BAD_ACCESS”.
8 s = nil;
9 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接口定义部分
1 #import < Foundation / Foundation.h >
2
3
4 @interface Shoe : NSObject {
5 NSString * _shoeColor;
6 int _shoeSize;
7 }
8
9 // 鞋子尺寸
10 - ( void ) setSize:( int ) size;
11 - ( int ) Size;
12
13 // 鞋子颜色
14 - ( void ) setColor:(NSString * ) color;
15 - (NSString * ) Color;
16
17 // 设置鞋子的颜色和尺码
18 - ( void ) setColorAndSize:(NSString * ) pColor shoeSize:( int ) pSize;
19
20 @end
Shoe.m实现部分
1 //
2 // Shoe.m
3 // MemoryManage_1
4 //
5 // Created by jimmy.yang on 11-2-19.
6 // Copyright 2011 __MyCompanyName__. All rights reserved.
7 //
8
9 #import " Shoe.h "
10
11
12 @implementation Shoe
13
14 // 构造函数
15 - (id)init
16 {
17 if (self = [super init]){
18 _shoeColor = @" black " ;
19 _shoeSize = 35 ;
20 }
21 NSLog( @" 一双 %@ %d码 的鞋子造好了! " ,_shoeColor,_shoeSize);
22 return (self);
23 }
24
25 - ( void ) setColor:(NSString * ) newColor
26 {
27 _shoeColor = newColor;
28 }
29
30 - (NSString * ) Color
31 {
32 return _shoeColor;
33 }
34
35 - ( void ) setSize:( int ) newSize
36 {
37 _shoeSize = newSize;
38 }
39
40 - ( int ) Size
41 {
42 return _shoeSize;
43 }
44
45 - ( void ) setColorAndSize:(NSString * )color shoeSize:( int )size
46 {
47 [self setColor:color];
48 [self setSize:size];
49 }
50
51
52 // 析构函数
53 - ( void ) dealloc
54 {
55 NSLog( @" %@ %d码的鞋子正在被人道毁灭! " ,_shoeColor,_shoeSize);
56 [super dealloc];
57 }
58
59
60 @end
Man.h定义部分
1 //
2 // Man.h
3 // MemoryManage_1
4 //
5 // Created by jimmy.yang on 11-2-20.
6 // Copyright 2011 __MyCompanyName__. All rights reserved.
7 //
8
9 #import < Foundation / Foundation.h >
10 #import " Shoe.h "
11
12
13 @interface Man : NSObject {
14 NSString * _name;
15 Shoe * _shoe;
16 }
17
18
19 - ( void ) setName:(NSString * ) name;
20 - (NSString * )Name;
21
22 - ( void ) wearShoe:(Shoe * ) shoe;
23 @end
Man.m实现部分
1 //
2 // Man.m
3 // MemoryManage_1
4 //
5 // Created by jimmy.yang on 11-2-20.
6 // Copyright 2011 __MyCompanyName__. All rights reserved.
7 //
8
9 #import " Man.h "
10
11
12 @implementation Man
13
14 // 构造函数
15 - (id)init
16 {
17 if (self = [super init]){
18 _name = @" no name " ;
19 }
20 NSLog( @" 新人\ " % @\ " 出生了! " ,_name);
21 return (self);
22 }
23
24
25 - ( void ) setName:(NSString * )newName
26 {
27 _name = newName;
28 }
29
30 - (NSString * )Name
31 {
32 return _name;
33 }
34
35 - ( void ) wearShoe:(Shoe * )shoe
36 {
37 _shoe = shoe;
38 }
39
40 // 析构函数
41 - ( void ) dealloc
42 {
43 NSLog( @" \ " % @\ " 要死了! " ,_name);
44 [super dealloc];
45 }
46
47 @end
main函数调用
1 #import < Foundation / Foundation.h >
2 #import " Shoe.h "
3 #import " Man.h "
4
5 int main ( int argc, const char * argv[]) {
6
7 Man * jimmy = [Man new ];
8 [jimmy setName: @" Jimmy " ];
9
10
11 Shoe * black40 = [Shoe new ];
12 [black40 setColorAndSize: @" Black " shoeSize: 40 ];
13
14 [jimmy wearShoe:black40];
15
16 [jimmy release];
17 [black40 release];
18
19 return 0 ;
20
21 }
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()方法,改成下面这样:
1 // 析构函数
2 - ( void ) dealloc
3 {
4 NSLog( @" \ " % @\ " 要死了! " ,_name);
5 [_shoe release]; // 这里释放_shoe
6 [super dealloc];
7 }
即:在Man被销毁的时候,先把_shoe给销毁。这样在main()函数中,就不再需要单独写一行[black40 release]来释放black40了.
现在又有新情况了:jimmy交了一个好朋友mike,二人成了铁哥们,然后jimmy决定把自己的鞋子black40,跟mike共同拥有,于是main函数就成了下面这样:
1 int main ( int argc, const char * argv[]) {
2
3 Man * jimmy = [Man new ];
4 [jimmy setName: @" Jimmy " ];
5
6 Shoe * black40 = [Shoe new ];
7 [black40 setColorAndSize: @" Black " shoeSize: 40 ];
8
9 [jimmy wearShoe:black40];
10
11 Man * mike = [Man new ];
12 [mike setName: @" mike " ];
13 [mike wearShoe:black40]; // mike跟jimmy,现在共同拥有一双40码黑色的鞋子
14
15 [jimmy release];
16 [mike release];
17
18 return 0 ;
19 }
麻烦来了: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抵消掉,反之亦然,否则,要么对象不会被销毁,要么过早销毁导致后面的非法引用而出错。
下一回,我们来看看如何用自动释放池来换一个方式来处理引用计数。