欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

IOS开发(40)之objective-C 的内存管理之-引用计数

程序员文章站 2023-11-07 11:48:58
obj-c本质就是"改进过的",大家都知道c语言是没有垃圾回收(gc)机制的(注:虽然obj-c2.0后来增加了gc功能,但是在iphone上不能用,因此对于i...

obj-c本质就是"改进过的",大家都知道c语言是没有垃圾回收(gc)机制的(注:虽然obj-c2.0后来增加了gc功能,但是在iphone上不能用,因此对于ios平台的程序员来讲,这个几乎没啥用),所以在obj-c中写程序时,对于资源的释放得由开发人员手动处理,相对要费心一些。

引用计数

这是一种古老但有效的内存管理方式。每个对象(特指:类的实例)内部都有一个retaincount的引用计数,对象刚被创建时,retaincount为1,可以手动调用retain方法使retaincount+1,同样也可以手动调用release方法使retaincount-1,调用release方法时,如果retaincount值减到0,将自动调用对象的dealloc方法(类似于中的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抵消掉,反之亦然,否则,要么对象不会被销毁,要么过早销毁导致后面的非法引用而出错。