objective-C 的内存管理之-实例分析
时间:2011-02-24 来源:菩提树下的杨过
场景:有二个类Car和Engine,即“汽车”和“引擎”。
先来看最初的版本:
Engine.h
#import <Cocoa/Cocoa.h> @interface Engine : NSObject @property int flag; @end // Engine
Engine.m
#import "Engine.h" @implementation Engine @synthesize flag; - (NSString *) description { return ([NSString stringWithFormat:@"I am engine %d,my retainCount=%d",flag,[self retainCount]]); } // description -(void) dealloc { NSLog(@"this engine %d is going to die.",flag); [super dealloc]; NSLog(@"this engine %d is dead.",flag); } @end // Engine
代码不复杂,略加解释:Engine类有一个flag属性,用于后面辅助输出时区分当前引擎的唯一标识。然后就是一个description方法(相当于c#中Object的toString()方法),用于返回一个描述自身的字符串。最后就是dealloc方法,用于清理自身所用的资源。
Car.h
#import <Cocoa/Cocoa.h> #import "Engine.h" @interface Car : NSObject { Engine *engine; } @property int flag; - (void) setEngine: (Engine *) newEngine; - (Engine *) engine; @end // Car
Car.m
#import "Car.h" #import "Engine.h" @implementation Car @synthesize flag; - (id) init { if (self = [super init]) { engine = [Engine new]; //每辆汽车诞生时,先预设了一个空的引擎(flag=0的engine),这个对象最终也需要释放! } return (self); } // init - (Engine *) engine { return (engine); } // engine - (void) setEngine: (Engine *) newEngine { engine = newEngine; } // setEngine -(void) dealloc { NSLog(@"the car %d is going to die.",flag); NSLog(@"%@",engine); [engine release];//释放附属资源:引擎 [super dealloc]; NSLog(@"the car %d is dead.",flag); } @end // Car
解释一下:init方法中,给每辆汽车在出厂时预置了一个默认的引擎(其flag值为默认值0),然后setEngine方法用于给汽车设置新引擎,最后dealloc中,汽车销毁时会附带release自己的引擎。
先来考虑第一种情况:
有一辆汽车,给它安装了新引擎,使用完后汽车销毁,但是引擎还能拿出来做其它用途(比如给其它汽车使用之类),最后新引擎也用完了,销毁!
Car *car1 = [Car new]; car1.flag = 1; Engine *engine1 = [Engine new]; engine1.flag = 1; [car1 setEngine:engine1]; [car1 release]; NSLog(@"%@",engine1);//这里模拟引擎做其它用途 [engine1 release];
问题至少有二个:
1.1 Car在构造函数init里,预置的默认引擎(即flag=0的引擎)最后未被释放
1.2 Car在dealloc方法中,已经释放了engine,所以Car释放后,该引擎也就跟着灰飞烟灭了,没办法再做其它用途。所以第7,8行代码根本没办法运行,会直接报错!这比内存泄漏更严重。
先来解决最严重的第2个问题,至少让它跑起来再说,根源在于:Car销毁时,附带把engine也给release了!解决它的途径有二种:
1、去掉Car.m类dealloc中的[engine release],但是本着“自家的孩子自己管”的原则,不推荐这种不负责任的做法。
2、在setEngine方法中,人工调用[newEngine retain]方法,让引擎的引用计数加1,这样正好可抵消Car.m类dealloc方法中[engine release]带来的影响(一加一减,正好抵消!)。
于是Car.m中的setEngine方法有了第二个版本:
- (void) setEngine: (Engine *) newEngine { engine = [newEngine retain]; } // setEngine
再次编译,总算通过了,也能运行了。先把问题1.1丢到一边,再来考虑第二种情况:
又有一辆汽车,安装了新引擎engine1,然后试了一下,觉得不爽,于是把engine1丢了,然后又换了另一个引擎engine2(喜新厌旧!)
Car *car1 = [Car new]; car1.flag = 1; Engine *engine1 = [Engine new]; engine1.flag = 1; [car1 setEngine:engine1];//换新引擎engine1 [engine1 release];//觉得不爽,于是把engine1扔了 Engine *engine2 = [Engine new]; engine2.flag = 2; [car1 setEngine:engine2];//又换了新引擎engine2 [car1 release];//使用完以后,car1报废 [engine2 release];//新引擎engine2当然也不再需要了
同样有二个问题:
2.1 engine1虽然被new了一次,然后在setEngine中又被retain了一次,也就是说其retainCount为2,虽然代码中后来release了一次,但是也只能让retainCount减到1,并不能销毁!
2.2 刚才1.1中所说的问题依然存在,即Car在init方法中预置的默认引擎engine0,始终被无视了,未得到解脱。
可能,你我都想到了,在setEngine方法中,可以先把原来的旧引擎给干掉,然后再把新引擎挂上去,这样就ok了! 好吧,setEngine的第三个版本出现了:
- (void) setEngine: (Engine *) newEngine { [engine release]; engine = [newEngine retain]; } // setEngine
貌似皆大欢喜了,但是事情还没完,又有新情况了:第三种情况
有二辆汽车Car1与Car2,Car1换了新引擎engine1,然后跑去跟Car2显摆,Car2觉得新引擎不错,于是要求跟Car1共用新引擎engine1,但问题是在Car2尚未下手前,engine1已经被Car1给抛弃了!
Engine *engine1 = [Engine new];//engine1.retainCount=1 engine1.flag = 1; Car *car1 = [Car new]; car1.flag = 1; Car *car2 = [Car new]; car2.flag = 2; [car1 setEngine:engine1];//car1换了新引擎engine1 [engine1 release];//然后很快又抛弃了它 [car2 setEngine:[car1 engine]];//car2要跟car1共用engine1 //最后car1跟car2都被车主main函数给扔了 [car2 release]; [car1 release];