2010-10-21 69 views
1

如果我想通過這是在主線程中創建到一個的NSOperation對象的對象,有什麼做這麼說我沒有創造任何內存管理問題的標準方式?我應該讓我的對象的屬性沒有'nonatomic'屬性嗎?傳遞對象到的NSOperation,並確保正確的內存管理策略

現在,我通過[[[AClass alloc] init] autorelease]分配對象,在主線程上保留實例副本,然後將另一個副本作爲NSArray的一部分傳入NSOperation。當我嘗試過的NSOperation類和ACLASS的屬性訪問一個內部數組列表對象進行迭代,調試器報告說ACLASS的實例對象的成員屬性的一個已經zombied,而有些則沒有。我看到的錯誤是:

-[CFString retain]: message sent to deallocated instance 0x5a8c6b0 
*** -[CFString _cfTypeID]: message sent to deallocated instance 0x5a8c6b0 
*** -[CFString _cfTypeID]: message sent to deallocated instance 0x5a8c6b0 

我不能說誰過早釋放我的字符串屬性,但整個對象實例尚未釋放。

我班的樣子:

@interface AClass 
{ 
    NSString *myTitle; 
    NSString *myDescription; 
} 

@property (nonatomic, retain, readonly) NSString *myTitle; 
@property (nonatomic, retain, readonly) NSString *myDescription; 

@end 

@implementation AClass 
@synthesize myTitle, myDescription; 
- (void)dealloc 
{ 
    [myTitle release]; 
    [myDescription release]; 
} 
@end 

回答

1

下面是一個有效的,「線程安全」 ACLASS的版本更新的片段:

/** 
    AClass is an immutable container: 
     - category methods must never change the state of AClass 
*/ 
@interface AClass : NSObject <NSCopying> 
{ 
@private 
    NSString * title; 
    NSString * description; 
} 
/** 
    subclassing notes: 
    - do not override properties: title, description 
    - implement @protocol NSCopying 
*/ 

/* 
1) document copy on entry here, even though the compiler has no 
     additional work to do. 
2) nonatomic in this case - these ivars initialized and never mutate. 
3) readonly because they are readonly 
*/ 
@property (copy, readonly, nonatomic) NSString * title; 
@property (copy, readonly, nonatomic) NSString * description; 

/* prohibited: */ 
- (id)init; 

/* designated initializer */ 
- (id)initWithTitle:(NSString *)inTitle description:(NSString *)inDescription; 

@end 

@implementation AClass 

@synthesize title; 
@synthesize description; 

- (id)init 
{ 
    assert(0 && "use the designated initializer"); 
    self = [super init]; 
    [self release]; 
    return 0; 
} 

- (id)initWithTitle:(NSString *)inTitle description:(NSString *)inDescription 
{ 
    self = [super init]; 
    assert(self && "uh oh, NSObject returned 0"); 

    if (0 != self) { 

     if (0 == inTitle || 0 == inDescription) { 
      assert(inTitle && inDescription && "AClass: invalid argument"); 
      [self release]; 
      return 0; 
     } 
      /* this would catch a zombie, if you were given one */ 
     title = [inTitle copy]; 
     description = [inDescription copy]; 

     if (0 == title || 0 == description) { 
      assert(title && description && "string failed to copy"); 
      [self release]; 
      return 0; 
     } 

    } 
    return self; 
} 

- (void)dealloc 
{ 
    /* which could also happen when if your init fails, but the assertion in init will be hit first */ 
    assert(title && description && "my ivars are not meant to be modified"); 

    [title release], title = 0; 
    [description release], description = 0; 

    /* don't forget to call through super at the end */ 
    [super dealloc]; 
} 


- (id)copyWithZone:(NSZone *)zone 
{ 
    assert(self.title == title && self.description == description && "the subclasser should not override the accessors"); 

    if ([self zone] == zone && [self class] == [AClass class]) { 
     /* 
     this is one possible (optional) optimization: 
      - avoid using this approach if you don't entirely understand 
      all the outlined concepts of immutable containers and low 
      level memory management in Cocoa and just use the 
      implementation in 'else' 
     */ 
     return [self retain]; 
    } 

    else { 
     return [[[self class] allocWithZone:zone] initWithTitle:self.title description:self.description]; 
    } 

} 

@end 

除此之外,避免過度使用autorelease的電話,以你的內存問題是本地調用點。此方法將解決許多問題(儘管您的應用程序中可能仍存在內存問題)。

更新在回答問題:

賈斯汀Galzic:所以基本上,複製 確保對象是本地的 主叫當實例共享 出上 NSOperations是線程上,他們是兩個 不同的實例?

實際上,調用不可變字符串的copy可能會執行retain

爲例:AClass可以現在只需retain荷蘭國際集團的2串實施@protocol NSCopying。同樣,如果你知道AClass從不子類,你可以只是返回[self retain]當物體在同一NSZone分配。

一個可變字符串被傳遞給的 AClass初始化

,那麼它將(當然)執行一個具體的副本。

如果你想要對象共享這些字符串,那麼這種方法是首選(在大多數情況下),因爲你(和所有使用AClass的客戶端)現在知道ivars永遠不會改變你的背後(他們指向的也是作爲字符串的內容)。當然,你仍然有能力在AClass的實現中改變titledescription指向的錯誤 - 這會破壞你建立的策略。如果你想改變AClass的成員,你必須使用鎖,@synchronized指令(或類似的東西) - 然後通常設置一些觀察回調,這樣可以保證你的類可以按預期工作。所有這些在大多數情況下都是不必要的,因爲上面的不可變接口在大多數情況下非常簡單。

回答你的問題:copy的調用不保證創建一個新的分配 - 它只是允許幾個保證傳播到客戶端,同時避免所有線程安全(和鎖定/同步)。

如果有一些情況下,你 確實需要多個類(在同一 線程)共享此對象嗎? 然後您將這個 對象的隱含副本,然後傳遞到 NSOperation?

現在我詳細說明了如何執行復制不可變對象。很顯然,在許多情況下,不可變對象的屬性(NSString,NSNumber等)應該聲明爲複製(但許多Cocoa程序員不會以這種方式聲明它們)。

如果你想共享NSString,你知道它是不可變的,你應該從AClass複製它。

,如果你想分享的AClass一個實例,你有兩個選擇:

1)(最佳)實施ACLASS @protocol NSCopying:上面添加- (id)copyWithZone:實施。

現在,客戶端可以免費使用copyretainAClass,因爲這對他們的需求最符合邏輯。 2)(壞)預計所有客戶端將保持其代碼是最新的,更改爲AClass,並根據需要使用copyretain。這是不現實的。它如果您的實施AClass需要更改,引入錯誤的好方法,因爲客戶端不會總是相應地更新他們的程序。有些人認爲這個對象在包中是私有的時候是可以接受的(例如,只有一個類使用並且看到它的接口)。

簡而言之,最好保持retaincopy的語義是可預測的 - 並且只需隱藏您的類中的所有實現細節,以便客戶的代碼永不中斷(或最小化)。

如果您的對象是真正共享的,並且其狀態是可變的,那麼請使用retain併爲狀態更改實現回調。否則,保持簡單並使用不可變的接口和具體的複製。

如果一個對象有一個不可變的狀態,那麼這個例子總是一個無鎖的線程安全實現,並且有許多保證。

NSOperation子類的實現,我覺得最好的(在大多數情況下)到: - 創建一個對象,它提供它所需要的所有方面(例如,URL加載) - 如果有什麼需要了解操作的結果或使用數據,然後爲回調創建一個@protocol接口,並將成員添加到由NSOperation子類保留的操作子類中,並且禁止在該子類的生命週期中指向另一個對象NSOperation實例:

@protocol MONImageRenderCallbackProtocol 

@required 
/** ok, the operation succeeded */ 
- (void)imageRenderOperationSucceeded:(AClass *)inImageDescriptor image:(NSImage *)image; 

@required 
/** bummer. the image request failed. see the @a error */ 
- (void)imageRenderOperationFailed:(AClass *)inImageDescriptor withError:(NSError *)error; 

@end 

/* MONOperation: do not subclass, create one instance per render request */ 
@interface MONOperation : NSOperation 
{ 
@private 
    AClass * imageDescriptor; /* never change outside initialization/dealloc */ 
    NSObject<MONImageRenderCallbackProtocol>* callback; /* never change outside initialization/dealloc */ 
    BOOL downloadSucceeded; 
    NSError * error; 
} 

/* designated initializer */ 
- (id)initWithImageDescriptor:(AClass *)inImageDescriptor callback:(NSObject<MONImageRenderCallbackProtocol>*)inCallback; 

@end 

@implementation MONOperation 

- (id)initWithImageDescriptor:(AClass *)inImageDescriptor callback:(NSObject<MONImageRenderCallbackProtocol>*)inCallback 
{ 
    self = [super init]; 
    assert(self); 
    if (0 != self) { 

     assert(inImageDescriptor); 
     imageDescriptor = [inImageDescriptor copy]; 

     assert(inCallback); 
     callback = [inCallback retain]; 

     downloadSucceeded = 0; 
     error = 0; 

     if (0 == imageDescriptor || 0 == callback) { 
      [self release]; 
      return 0; 
     } 
    } 
    return self; 
} 

- (void)dealloc 
{ 
    [imageDescriptor release], imageDescriptor = 0; 
    [callback release], callback = 0; 
    [error release], error = 0; 

    [super dealloc]; 
} 

/** 
@return an newly rendered NSImage, created based on self.imageDescriptor 
will set self.downloadSucceeded and self.error appropriately 
*/ 
- (NSImage *)newImageFromImageDescriptor 
{ 
    NSImage * result = 0; 
    /* ... */ 
    return result; 
} 

- (void)main 
{ 
    NSAutoreleasePool * pool = [NSAutoreleasePool new]; 

    NSImage * image = [self newImageFromImageDescriptor]; 

    if (downloadSucceeded) { 
     assert(image); 
     assert(0 == error); 
     [callback imageRenderOperationSucceeded:imageDescriptor image:image]; 
     [image release], image = 0; 
    } 
    else { 
     assert(0 == image); 
     assert(error); 
     [callback imageRenderOperationFailed:imageDescriptor withError:error]; 
    } 

    [pool release], pool = 0; 
} 

@end 
+0

所以基本上,副本確保對象對調用者來說是本地的,而當實例是沙紅色到NSOperations所在的線程,它們是兩個不同的實例?如果在某些情況下您確實需要多個類(在同一個線程中)共享此對象,該怎麼辦?你會然後做一個這個對象的隱式副本,然後傳遞給NSOperation? – 2010-10-21 18:55:27

+0

如果我想保留保留策略並在我的主線程中共享單個實例,那麼在AClass上執行深層副本並將副本傳遞到NSOperation類是否合適? – 2010-10-21 19:16:28

+0

Galzic查看最新回覆 – justin 2010-10-21 21:15:12

0

如果有什麼我不推薦,它是你保持你參考以知識不保留,這將別人已被保留。因爲引用是未保留的,所以當它從數組中移除時,它將被釋放,保留計數可能會降到零,並且對象可能是dealloc,現在您持有定時炸彈,即無效引用。

我建議不自動釋放參考,並嘗試在你的dealloc做的破發點,看到堆棧調用,看看誰使你的對象是dealloc的。

相關問題