objective-C 的内存管理之-引用计数

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抵消掉,反之亦然,否则,要么对象不会被销毁,要么过早销毁导致后面的非法引用而出错。

下一回,我们来看看如何用自动释放池来换一个方式来处理引用计数。

时间: 2024-12-21 17:37:47

objective-C 的内存管理之-引用计数的相关文章

Swift编程中用以管理内存的自动引用计数详解_Swift

Swift 内存管理功能是通过使用自动引用计数(ARC)来处理.ARC用于初始化和取消初始化所述系统资源,从而释放使用的类实例的存储器空间当实例不再需要.ARC跟踪代码的实例有效地管理存储资源之间的关系的信息. ARC的功能 在每一次一个新的类实例被创建时ARC分配一块内存以存储信息 init() 关于实例类型和其值的信息存储在存储器中 当类实例不再需要它自动由 deinit() 释放,用于进一步类实例的存储和检索的存储空间 ARC保存在磁道当前参照类实例的属性,常量和变量,使得 deinit(

iPhone开发内存管理

  开发iPhone 应用程序并不难,基本上就是三个词 - "memory, memory, memory" .iPhone OS 对内存的要求很严格,有memory leak ,杀掉; 内存使用超限额,杀掉.一个经过测试的程序,在使用过程中90%以上的崩溃都是内存问题造成的.在这里简单总结一下Object-C 内存管理. 基本概念 Object-C 的内存管理基于引用计数(Reference Count)这种非常常用的技术.简单讲,如果要使用一个对象,并希望确保在使用期间对象不被释放

iOS/OS X 内存管理(二):借助工具解决内存问题

上一篇博客iOS/OS X内存管理(一):基本概念与原理主要讲了iOS/OSX 内存管理中引用计数和内存管理规则,以及引入ARC新的内存管理机制之后如何选择ownership qualifiers(__strong.__weak.__unsafe_unretained和__autoreleasing)来管理内存.这篇我们主要关注在实际开发中会遇到哪些内存管理问题,以及如何使用工具来调试和解决. 在往下看之前请下载实例MemoryProblems,我们将以这个工程展开如何检查和解决内存问题. 悬挂

内存管理内幕

动态分配的选择.折衷和实现 Jonathan Bartlett (johnnyb@eskimo.com), 技术总监, New Media Worx 本文将对 Linux 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言.文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存. 好文章收藏! 原文地址:http://www.ibm.com/developerwork

autorelease-关于oc引用计数的问题

问题描述 关于oc引用计数的问题 UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)] autorelease]; returnLable = label; NSLog(@"label = %d",returnLable.retainCount);//计数是1 [self.view addSubview:returnLable]; NSLog(@"label = %d&

iOS开发系列—Objective-C之内存管理

概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存.其他高级语言如C#.Java都是通过垃圾回收来(GC)解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护.今天将着重介绍ObjC内存管理: 引用计数器 属性参数 自动释放池 引用计数器 在Xcode4.2及之后的版本中

IOS中内存管理那些事_IOS

Objective-C 和 Swift 语言的内存管理方式都是基于引用计数「Reference Counting」的,引用计数是一个简单而有效管理对象生命周期的方式.引用计数分为手动引用计数「ARC: AutomaticReference Counting」和自动引用计数「MRC: Manual Reference Counting」,现在都是用 ARC 了,但是我们还是很有必要了解 MRC. 1. 引用计数的原理是什么? 当我们创建一个新对象时,他的引用计数为1: 当有一个新的指针指向这个对象

理解iOS的内存管理

远古时代的故事 那些经历过手工管理内存(MRC)时代的人们,一定对 iOS 开发中的内存管理记忆犹新.那个时候大约是 2010 年,国内 iOS 开发刚刚兴起,tinyfool 大叔的大名已经如雷贯耳,而我还是一个默默无闻的刚毕业的小子.那个时候的 iOS 开发过程是这样的: 我们先写好一段 iOS 的代码,然后屏住呼吸,开始运行它,不出所料,它崩溃了.在 MRC 时代,即使是最牛逼的 iOS 开发者,也不能保证一次性就写出完美的内存管理代码.于是,我们开始一步一步调试,试着打印出每个怀疑对象的

Cocos2d-x的内存管理总结_Android

Cocos2d-x引擎的核心是用C++编写的,那对于所有使用该引擎的游戏开发人员来说,内存管理是一道绕不过去的坎. 关于Cocos2d-x内存管理,网上已经有了许多参考资料,有些资料写的颇为详实,因为在内存管理这块我不想多费笔墨,只是更多的将思路描述清楚. 一.对象内存引用计数 Cocos2d-x内存管理的基本原理就是对象内存引用计数,Cocos2d-x将内存引用计数的实现放在了顶层父类CCObject中,这里将涉及引用计数的CCObject的成员和方法摘录出来: 复制代码 代码如下: clas