zoukankan      html  css  js  c++  java
  • objectiveC 的内存管理之引用计数

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

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

    作者:菩提树下的杨过
    出处:http://yjmyzz.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    人月神话阅读笔记之二
    第二阶段:团队开发Fooks第三天
    第二阶段:团队开发Fooks第二天
    第二阶段:团队开发Fooks第一天
    第十三周进度总结
    人月神话阅读笔记之一
    第一阶段意见评论
    拓扑排序
    欧拉回路
    矩阵优化
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/1958961.html
Copyright © 2011-2022 走看看