下面是一個有效的,「線程安全」 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
的實現中改變title
和description
指向的錯誤 - 這會破壞你建立的策略。如果你想改變AClass
的成員,你必須使用鎖,@synchronized
指令(或類似的東西) - 然後通常設置一些觀察回調,這樣可以保證你的類可以按預期工作。所有這些在大多數情況下都是不必要的,因爲上面的不可變接口在大多數情況下非常簡單。
回答你的問題:copy
的調用不保證創建一個新的分配 - 它只是允許幾個保證傳播到客戶端,同時避免所有線程安全(和鎖定/同步)。
如果有一些情況下,你 確實需要多個類(在同一 線程)共享此對象嗎? 然後您將這個 對象的隱含副本,然後傳遞到 NSOperation?
現在我詳細說明了如何執行復制不可變對象。很顯然,在許多情況下,不可變對象的屬性(NSString
,NSNumber
等)應該聲明爲複製(但許多Cocoa程序員不會以這種方式聲明它們)。
如果你想共享NSString
,你知道它是不可變的,你應該從AClass
複製它。
,如果你想分享的AClass
一個實例,你有兩個選擇:
1)(最佳)實施ACLASS @protocol NSCopying
:上面添加- (id)copyWithZone:
實施。
現在,客戶端可以免費使用copy
和retain
AClass
,因爲這對他們的需求最符合邏輯。 2)(壞)預計所有客戶端將保持其代碼是最新的,更改爲AClass
,並根據需要使用copy
或retain
。這是不現實的。它是如果您的實施AClass
需要更改,引入錯誤的好方法,因爲客戶端不會總是相應地更新他們的程序。有些人認爲這個對象在包中是私有的時候是可以接受的(例如,只有一個類使用並且看到它的接口)。
簡而言之,最好保持retain
和copy
的語義是可預測的 - 並且只需隱藏您的類中的所有實現細節,以便客戶的代碼永不中斷(或最小化)。
如果您的對象是真正共享的,並且其狀態是可變的,那麼請使用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
所以基本上,副本確保對象對調用者來說是本地的,而當實例是沙紅色到NSOperations所在的線程,它們是兩個不同的實例?如果在某些情況下您確實需要多個類(在同一個線程中)共享此對象,該怎麼辦?你會然後做一個這個對象的隱式副本,然後傳遞給NSOperation? – 2010-10-21 18:55:27
如果我想保留保留策略並在我的主線程中共享單個實例,那麼在AClass上執行深層副本並將副本傳遞到NSOperation類是否合適? – 2010-10-21 19:16:28
Galzic查看最新回覆 – justin 2010-10-21 21:15:12