這個設置實際上做了一些非常有趣的事情,並提出了一些有關Objective-C內存管理的優點。讓我們首先重申代碼:
// Testing.h
@interface Testing : NSObject {
NSMutableString *retainString;
NSMutableString *copyString;
}
@property(nonatomic,retain) NSMutableString *retainString;
@property(nonatomic,copy) NSMutableString *copyString;
// Testing.m
@implementation Testing
@synthesize retainString, copyString;
- (id)init {
if(self = [super init]) {
NSMutableString *test = [[NSMutableString alloc] init];
NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
self.retainString = test;
NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
self.copyString = test;
NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
[self.copyString appendFormat:@"test"];
NSLog(@"test %d; retain %d; copy %d", [test retainCount], [retainString retainCount], [copyString retainCount]);
}
return self;
}
@end
這將產生日誌輸出:
2009-12-24 03:35:01.408 RetainCountTesting[1429:40b] test 1; retain 0; copy 0
2009-12-24 03:35:01.410 RetainCountTesting[1429:40b] test 2; retain 2; copy 0
2009-12-24 03:35:01.410 RetainCountTesting[1429:40b] test 2; retain 2; copy 2147483647
2009-12-24 03:35:01.413 RetainCountTesting[1429:40b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendFormat:'
所以這是怎麼回事呢?前兩個電話相當簡單:
- 到
alloc
/init
初始呼叫建立與保留計數1,符合市場預期新的NSMutableString對象。我們有一個保留在其上的對象。
- 編輯
retain
ed屬性會按預期遞增保留計數。我們有一個有兩個保留的對象。
這是它變得奇怪的地方。 copy
屬性的賦值的確確實實施了一個副本,但並不如預期的那樣。 NSString和NSMutableString是類集羣的一部分 - 當您創建或修改字符串時,它可能是也可能不是您期望的類的實例。該語言可能會將其變爲幕後的其他表示形式。
在這種特殊情況下,當執行復制時,顯然語言決定該字符串(因爲它不包含任何信息)被認爲是不可變的,並且使其成爲可能。當人們做一些像[[NSString alloc] initWithString:@"hello"]
這樣的事情時經常會看到這是一個常量,靜態字符串,因此不需要動態分配對象。保持靜態可以幫助運行時更好地運行。
所以現在我們有兩個對象:我們原來的test
對象被保留兩次,而新對象是靜態的,因此保留計數爲INT_MAX
。最後,由於新字符串是不可變的,因此調用它的增變器方法會殺死程序。另外,將原來的電話從init
更改爲initWithString:
的確會使複製分配按預期執行(有點) - 複製對象上的保留計數僅爲1,但仍不能對其進行變異。再說一次,這可能是由於編譯器中的一些優化魔法,它決定該字符串是靜態的,並且如果沒有必要,沒有理由使其變爲可變的。
要回答您的最終問題:是,您可以在這些對象中的任何一個上調用release
。它不會做太多。充其量,你將銷燬複製的對象(因爲它的保留計數爲1);在最壞的情況下,它不會對靜態字符串對象產生任何影響。不過,我建議繼續通過屬性工作:而不是釋放複製的對象,爲什麼不只是做self.copyString = nil;
?由於它調用屬性設置器,因此它會根據需要來處理釋放,然後您沒有指向仍然在其周圍浮動的對象的指針。
有關這一切的更多信息,可以閱讀:
來源
2009-12-24 09:48:19
Tim
很好的答案,Tim。 – gavinb 2009-12-24 10:29:03
不需要爲這種情況下的編譯器優化。該文檔指出,複製屬性屬性調用對象的複製方法。定義此方法的NSCopying協議聲明它將返回一個不可變對象(如果適用於該類)。要獲得可變副本,該屬性將不得不調用mutableCopy。因此,複製屬性將始終複製一個不可變的對象,除了實現調用mutableCopy的自己的setter外,目前還沒有辦法解決這個問題。 – Adrian 2009-12-24 21:29:59
Adrian:謝謝你的信息! – Tim 2009-12-25 05:40:52