2009-12-24 129 views
1

我有一個非常明確的問題:iPhone內存管理(專爲屬性)

//.h file 

@property (nonatomic, retain)NSMutableString * retainString; 
@property (nonatomic, copy)NSMutableString * copyString; 

//.m file 
@synthesis retainString, copyString; 
-(void)Process 
{ 
    NSMutableString *test = [[NSMutableString alloc]inti];//retain count should be 1 

    self.retainString = test; 

    self.copyString = test; 

} 

COND。 1-> //兩者的保留計數應爲2.因爲它們指向的保留計數爲2的同一內存位置,因此應如何寫入釋放。

cond。 2-> // //保留測試的計數爲1並且copyString爲2.由於兩者都保存不同的內存位置。但是我們可以寫[copyString release]。

回答

1

如果你有關鍵字'retain'或'copy'定義的屬性,你應該總是在dealloc方法中釋放相應的成員變量。

在這種情況下,你的dealloc應該是這樣的:

- (void)dealloc { 
    [retainString release]; 
    [copyString release]; 

    [super dealloc]; 
} 

現在,當你alloc在自定義類的方法的字符串,這種方法擁有作爲內存管理編程指南中描述的字符串可可。這意味着你應該在release之前離開該方法的字符串。

@synthesize retainString, copyString; 

- (void)Process { 
    NSMutableString *test = [[NSMutableString alloc] init]; //retain count is 1 

    self.retainString = test;  // retain count of test is 2 
    self.copyString = test;  // test's retain count = 2, copyString's = 1 

    [test release];    // retain count of test is 1 again 
} 

當這個類的一個實例被破壞,dealloc方法將被調用這將反過來release兩個字符串。只要他們保留的數量保持爲1,他們也將是dealloc'ed

9

這個設置實際上做了一些非常有趣的事情,並提出了一些有關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;?由於它調用屬性設置器,因此它會根據需要來處理釋放,然後您沒有指向仍然在其周圍浮動的對象的指針。

有關這一切的更多信息,可以閱讀:

+0

很好的答案,Tim。 – gavinb 2009-12-24 10:29:03

+2

不需要爲這種情況下的編譯器優化。該文檔指出,複製屬性屬性調用對象的複製方法。定義此方法的NSCopying協議聲明它將返回一個不可變對象(如果適用於該類)。要獲得可變副本,該屬性將不得不調用mutableCopy。因此,複製屬性將始終複製一個不可變的對象,除了實現調用mutableCopy的自己的setter外,目前還沒有辦法解決這個問題。 – Adrian 2009-12-24 21:29:59

+0

Adrian:謝謝你的信息! – Tim 2009-12-25 05:40:52