Quando um object associado é liberado?

Estou anexando o object B via referência associativa ao object A. O object B observa algumas propriedades do object A através do KVO.

O problema é que o object B parece ser desalocado após o object A, o que significa que é tarde demais para se remover como um observador KVO do object A. Eu sei disso porque estou obtendo exceções NSKVODeallocateBreak, seguidas por falhas EXEC_BAD_ACCESS no dealloc do object B.

Alguém sabe por que o object B é desalocado após o object A com OBJC_ASSOCIATION_RETAIN? Os objects associados são liberados após a desalocação? Eles conseguem autoreleased? Alguém sabe de uma maneira de alterar esse comportamento?

Eu estou tentando adicionar algumas coisas a uma class através de categorias, por isso não posso replace quaisquer methods existentes (incluindo dealloc), e eu particularmente não quero mexer com swizzling. Eu preciso de algum modo para desassociar e liberar o object B antes que o object A seja desalocado.

EDIT – Aqui está o código que estou tentando trabalhar. Se os objects associados fossem liberados antes de o UIImageView ser completamente desalocado, tudo isso funcionaria. A única solução que vejo é misturar o método do meu próprio dealloc e atualizar o original para acessá-lo. Isso fica realmente confuso.

O ponto da class ZSPropertyWatcher é que o KVO requer um método de retorno de chamada padrão, e eu não quero replace o UIImageView, caso ele use um.

UIImageView + Loading.h

@interface UIImageView (ZSShowLoading) @property (nonatomic) BOOL showLoadingSpinner; @end 

UIImageView + Loading.m

 @implementation UIImageView (ZSShowLoading) #define UIIMAGEVIEW_SPINNER_TAG 862353453 static char imageWatcherKey; static char frameWatcherKey; - (void)zsShowSpinner:(BOOL)show { if (show) { UIActivityIndicatorView *spinnerView = (UIActivityIndicatorView *)[self viewWithTag:UIIMAGEVIEW_SPINNER_TAG]; if (!spinnerView) { spinnerView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease]; spinnerView.tag = UIIMAGEVIEW_SPINNER_TAG; [self addSubview:spinnerView]; [spinnerView startAnimating]; } [spinnerView setEvenCenter:self.boundsCenter]; } else { [[self viewWithTag:UIIMAGEVIEW_SPINNER_TAG] removeFromSuperview]; } } - (void)zsFrameChanged { [self zsShowSpinner:!self.image]; } - (void)zsImageChanged { [self zsShowSpinner:!self.image]; } - (BOOL)showLoadingSpinner { ZSPropertyWatcher *imageWatcher = (ZSPropertyWatcher *)objc_getAssociatedObject(self, &imageWatcherKey); return imageWatcher != nil; } - (void)setShowLoadingSpinner:(BOOL)aBool { ZSPropertyWatcher *imageWatcher = nil; ZSPropertyWatcher *frameWatcher = nil; if (aBool) { imageWatcher = [[[ZSPropertyWatcher alloc] initWithObject:self keyPath:@"image" delegate:self callback:@selector(zsImageChanged)] autorelease]; frameWatcher = [[[ZSPropertyWatcher alloc] initWithObject:self keyPath:@"frame" delegate:self callback:@selector(zsFrameChanged)] autorelease]; [self zsShowSpinner:!self.image]; } else { // Remove the spinner [self zsShowSpinner:NO]; } objc_setAssociatedObject( self, &imageWatcherKey, imageWatcher, OBJC_ASSOCIATION_RETAIN ); objc_setAssociatedObject( self, &frameWatcherKey, frameWatcher, OBJC_ASSOCIATION_RETAIN ); } @end 

ZSPropertyWatcher.h

 @interface ZSPropertyWatcher : NSObject { id delegate; SEL delegateCallback; NSObject *observedObject; NSString *keyPath; } @property (nonatomic, assign) id delegate; @property (nonatomic, assign) SEL delegateCallback; - (id)initWithObject:(NSObject *)anObject keyPath:(NSString *)aKeyPath delegate:(id)aDelegate callback:(SEL)aSelector; @end 

ZSPropertyWatcher.m

 @interface ZSPropertyWatcher () @property (nonatomic, assign) NSObject *observedObject; @property (nonatomic, copy) NSString *keyPath; @end @implementation ZSPropertyWatcher @synthesize delegate, delegateCallback; @synthesize observedObject, keyPath; - (id)initWithObject:(NSObject *)anObject keyPath:(NSString *)aKeyPath delegate:(id)aDelegate callback:(SEL)aSelector { if (!anObject || !aKeyPath) { // pre-conditions self = nil; return self; } self = [super init]; if (self) { observedObject = anObject; keyPath = aKeyPath; delegate = aDelegate; delegateCallback = aSelector; [observedObject addObserver:self forKeyPath:keyPath options:0 context:nil]; } return self; } - (void)dealloc { [observedObject removeObserver:self forKeyPath:keyPath]; [keyPath release]; [super dealloc]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [self.delegate performSelector:self.delegateCallback]; } @end 

A resposta aceita para essa questão relacionada explica o cronograma de desalocação dos objects. O resultado é: Objetos associados são liberados após o término do método dealloc do object original.

Ainda maior que o seu problema -dealloc é este:

O UIKit não é compatível com KVO

Nenhum esforço foi feito para tornar o valor-chave das classs UIKit observável. Se algum deles for , é mera coincidência e está sujeito a quebra ao capricho da Apple. E sim, eu trabalho para a Apple no framework UIKit.

Isso significa que você terá que encontrar outra maneira de fazer isso, provavelmente alterando um pouco o layout da visualização.

O que eu acho que está acontecendo no seu caso é isso:

1) o object A recebe a chamada -dealloc , depois que sua contagem de -dealloc foi para 0;

2) o mecanismo de associação garante que o object B seja liberado (que é diferente de desalocado) em algum momento como conseqüência.

isto é, não sabemos exatamente em que ponto, mas parece provável que esse tipo de diferença semântica seja a causa do object B ser desalocado após o object A; O seletor do object A -dealloc não pode estar ciente da associação, portanto, quando o último release é chamado, -dealloc é executado e somente depois disso o mecanismo de associação pode enviar um -release para o object B …

também dê uma olhada neste post .

também afirma:

Agora, quando objectToBeDeallocated for desalocado, objectWeWantToBeReleasedWhenThatHappens será enviado automaticamente uma mensagem de liberação.

Espero que isso ajude a explicar o que você está experimentando. Quanto ao resto, não posso ser de muita ajuda …

EDIT: apenas para continuar com uma especulação tão interessante após o comentário por DougW …

Eu vejo o risco de ter uma espécie de dependência cíclica se o mecanismo de associação estiver “quebrado” ao liberar o object A (para continuar com o seu exemplo).

  1. se o código relacionado à associação fosse executado a partir do método de liberação (em vez de dealloc), para cada release você verificaria se o object “proprietário” (object A) tem uma contagem de retenção de 1; de fato, nesse caso, você sabe que diminuir sua contagem de retenções acionaria o dealloc, portanto, antes de fazer isso, você primeiro liberaria o object associado (object B em seu exemplo);

  2. mas o que aconteceria caso o object B também “possuísse” um terceiro object, digamos C? o que aconteceria é que no momento em que o release é chamado no object B, quando o object B reter count é 1, C será liberado;

  3. agora, considere o caso que o object C estava “possuindo” o primeiro da seqüência, object A. se, ao receber a liberação acima, C tivesse uma contagem de retenções de 1, primeiro tentaria liberar seu object associado, que é um;

    1. mas a contagem de release de A ainda é 1, então outra liberação seria enviada para B, que ainda tem uma contagem de retenções de 1; e assim por diante, em um loop.

Se você, por outro lado, enviar a liberação do -dealloc, tal dependência cíclica não parece possível.

É bem artificial e não tenho certeza se meu raciocínio está correto, então fique à vontade para comentar sobre isso …

objc_getAssociatedObject() para uma associação OBJC_ASSOCIATION_RETAIN retorna um object autoreleased. Você pode chamá-lo anteriormente no mesmo escopo de pool de ciclo de execução / autorelease quando o object A é desalocado? (Você provavelmente pode testar isso rapidamente alterando a associação para NONATOMIC ).