Вот обновленный фрагмент кода для эффективного, «потокобезопасного» версии 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
вызовов, так что ваши проблемы с памятью являются локальными для callsite. Такой подход позволит решить многие проблемы (хотя в вашем приложении все еще могут быть проблемы с памятью).
Обновление в ответ на вопросы:
Джастин Galzic: так в основном, копия гарантирует, что объекты являются локальными для вызывающего абонента и, когда экземпляр разделяется к нити, на которой NSOperations является на, они два разных экземпляров?
Фактически copy
вызов непреложной строки может выполнять retain
.
как пример: AClass
может Теперь реализовать @protocol NSCopying
просто retain
ИНГ на 2 строки. также, если вы знаете, что AClass
никогда не подклассифицировано, вы можете просто вернуть [self retain]
, когда объекты выделены в том же NSZone
.
Если переменная строка передается инициализатору AClass
, то она (конечно) выполнит конкретную копию.
Если вы хотите, чтобы объекты делились этими строками, то этот подход предпочтительнее (в большинстве случаев), потому что вы (и все клиенты, использующие AClass
) теперь знаете, что ивары никогда не изменятся за вашей спиной (на что они указывают, а также как содержимое строк). конечно, у вас все еще есть возможность совершить ошибку, изменив то, что title
и description
указывают на реализацию AClass
- это нарушит установленную вами политику. если вы хотите изменить то, что указали члены AClass
, вам придется использовать блокировки, директивы @synchronized
(или что-то подобное), а затем, как правило, настроить некоторые обратные вызовы, чтобы вы могли гарантировать, что ваш класс будет работать как ожидалось ... все это не является необходимым для большинства случаев, потому что вышеупомянутый неизменяемый интерфейс в большинстве случаев совершенно прост.
, чтобы ответить на ваш вопрос: вызов copy
не гарантирует создание нового распределения - он просто позволяет нескольким гарантиям распространяться на клиентов, избегая при этом любой безопасности потока (и блокировки/синхронизации).
Что делать, если есть некоторые случаи, когда вы действительно хотите несколько классов (на одной и той же нити), чтобы совместно использовать этот объект? Будет ли вы затем сделаете неявную копию этого объекта , а затем перейдете к NSOperation?
Теперь, когда я подробно рассказал о том, как можно копировать неизменяемые объекты. должно быть очевидно, что свойства неизменяемых объектов (NSString
, NSNumber
и т. д.) во многих случаях должны быть объявлены копией (но многие программисты какао не объявляют их таким образом).
Если вы хотите поделиться NSString
, который, как вы знаете, неизменен, вам следует просто скопировать его с AClass
.
, если вы хотите поделиться экземпляр AClass
у вас есть 2 варианта:
1) (лучший) реализации @protocol NSCopying
в AClass: добавлены выше - (id)copyWithZone:
реализации.
Теперь клиент свободен от copy
и retain
AClass
как наиболее логичен для их нужд.
2) (BAD) ожидает, что все клиенты будут обновлять свой код с изменениями до 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
так что в основном, копия гарантирует, что объекты являются локальными для вызывающего и когда экземпляр sha красный в поток, на котором работает NSOperations, это два разных экземпляра? Что делать, если есть некоторые случаи, когда вы хотите, чтобы несколько классов (в одном потоке) делили этот объект? Вы могли бы сделать неявную копию этого объекта, а затем перейти к NSOperation? –
Выполнение глубокой копии в AClass и передача копии в класс NSOperation были бы подходящими, если бы я хотел сохранить политику сохранения и поделиться одним экземпляром по моему основному потоку? –
Galzic см. Обновленный ответ – justin