2010-10-21 1 views
1

Если я хочу передать объект, который был создан в основном потоке, на объект NSOperation, что стандартный способ сделать так, чтобы я не создавал проблем с управлением памятью ? Должен ли я создавать свойства объекта не так называемый «неатомный» атрибут?Передача объектов в NSOperation и обеспечение правильных политик управления памятью

Прямо сейчас, я выделяю объекты через [[[AClass alloc] init] autorelease], сохраняю копию экземпляра в основном потоке, а затем передаю другую копию в NSOperation как часть NSArray. Когда я пытаюсь выполнить итерацию по объектам списка массивов внутри класса NSOperation и получить доступ к одному из свойств AClass, отладчик сообщает, что один из свойств элемента объекта экземпляра AClass уже зомбирован, а другие нет. Ошибка я вижу это:

-[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 вызовов, так что ваши проблемы с памятью являются локальными для 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 и retainAClass как наиболее логичен для их нужд.

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 
+0

так что в основном, копия гарантирует, что объекты являются локальными для вызывающего и когда экземпляр sha красный в поток, на котором работает NSOperations, это два разных экземпляра? Что делать, если есть некоторые случаи, когда вы хотите, чтобы несколько классов (в одном потоке) делили этот объект? Вы могли бы сделать неявную копию этого объекта, а затем перейти к NSOperation? –

+0

Выполнение глубокой копии в AClass и передача копии в класс NSOperation были бы подходящими, если бы я хотел сохранить политику сохранения и поделиться одним экземпляром по моему основному потоку? –

+0

Galzic см. Обновленный ответ – justin

0

Если есть что-то я не рекомендую, это то, что вы храните ваши ссылки unretained знанием того, что она будет сохранена кем-то еще уже. Поскольку ссылка не будет получена, когда она будет удалена из массива, она будет отпущена, значение count может упасть до нуля, а объект может быть dealloc, и теперь вы держите бомбу замедленного действия, т. Е. Неверную ссылку.

Я бы предположил, что не авторезистируйте ссылку и попробуйте сделать точку прерывания на вашем dealloc и посмотреть вызов стека, чтобы узнать, кто заставляет ваш объект быть dealloc.

Смежные вопросы