註:這是《Objective-C基礎教程》一書上的執行個體,但是原書限於篇幅,分析得比較簡單,初次閱讀看得比較費勁,這裡展開詳細討論一下。
情境:有二個類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自己,也可能是車主main()函數)給拋棄了!
Engine *engine1 = [Engine new];//engine1.retainCount=1engine1.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];
問題:在16行[car2 release]時,car2已經徹底把engine1給銷毀了(也許car2忘記了,engine1是它跟car1共同的財產),於是緊接著[car1 release]時,car1的dealloc方法在[engine release]時,意外發現engine1已經不在人世了,最終它憤怒了,整個程式也就罷工了!
setEngine的最後一個版本
- (void) setEngine: (Engine *) newEngine{[newEngine retain];[engine release];engine = newEngine; } // setEngine
其實就是把上一個版本的二行代碼,拆分成了三行,變成了先retain,再release,看上去好象含義一樣,但是仔細分析你會發現,如果當engine與newEngine為同一個對象的引用時(即這二指標指向的為同一塊記憶體),且newEngine(其實也就是engine)的retainCount為1時,原來的版本會導致newEngine(其實也就是engine)銷毀,而現在這樣處理後,即會被保留下來。
最後驗證一個最終版本是否能完美應付上面提到的三種情況:
第一種情況的運行結果:
2011-02-25 09:17:52.951 CarParts[257:a0f] this engine 0 is going to die.
2011-02-25 09:17:52.957 CarParts[257:a0f] this engine 0 is dead.
2011-02-25 09:17:52.959 CarParts[257:a0f] the car 1 is going to die.
2011-02-25 09:17:52.961 CarParts[257:a0f] I am engine 1,my retainCount=2
2011-02-25 09:17:52.962 CarParts[257:a0f] the car 1 is dead.
2011-02-25 09:17:52.966 CarParts[257:a0f] I am engine 1,my retainCount=1
2011-02-25 09:17:52.968 CarParts[257:a0f] this engine 1 is going to die.
2011-02-25 09:17:52.969 CarParts[257:a0f] this engine 1 is dead.
第二種情況的運行結果:
2011-02-25 09:19:30.639 CarParts[291:a0f] this engine 0 is going to die.
2011-02-25 09:19:30.644 CarParts[291:a0f] this engine 0 is dead.
2011-02-25 09:19:30.646 CarParts[291:a0f] this engine 1 is going to die.
2011-02-25 09:19:30.648 CarParts[291:a0f] this engine 1 is dead.
2011-02-25 09:19:30.650 CarParts[291:a0f] the car 1 is going to die.
2011-02-25 09:19:30.652 CarParts[291:a0f] I am engine 2,my retainCount=2
2011-02-25 09:19:30.653 CarParts[291:a0f] the car 1 is dead.
2011-02-25 09:19:30.655 CarParts[291:a0f] this engine 2 is going to die.
2011-02-25 09:19:30.657 CarParts[291:a0f] this engine 2 is dead.
第三種情況的運行結果:
2011-02-25 09:21:02.549 CarParts[324:a0f] this engine 0 is going to die.
2011-02-25 09:21:02.554 CarParts[324:a0f] this engine 0 is dead.
2011-02-25 09:21:02.556 CarParts[324:a0f] this engine 0 is going to die.
2011-02-25 09:21:02.558 CarParts[324:a0f] this engine 0 is dead.
2011-02-25 09:21:02.559 CarParts[324:a0f] the car 2 is going to die.
2011-02-25 09:21:02.561 CarParts[324:a0f] I am engine 1,my retainCount=2
2011-02-25 09:21:02.563 CarParts[324:a0f] the car 2 is dead.
2011-02-25 09:21:02.571 CarParts[324:a0f] the car 1 is going to die.
2011-02-25 09:21:02.573 CarParts[324:a0f] I am engine 1,my retainCount=1
2011-02-25 09:21:02.575 CarParts[324:a0f] this engine 1 is going to die.
2011-02-25 09:21:02.578 CarParts[324:a0f] this engine 1 is dead.
2011-02-25 09:21:02.587 CarParts[324:a0f] the car 1 is dead.
從輸出結果上看,不管是哪一種情況,Car以及Engine資源最終都得到了釋放!