Я привязываю объект B через ассоциативную ссылку на объект A. Объект B наблюдает некоторые свойства объекта A через KVO.
Проблема в том, что объект B, по-видимому, освобождается после объекта A, что означает его слишком поздно, чтобы удалить себя как наблюдателя KVO объекта A. Я знаю это, потому что получаю исключения NSKVODeallocateBreak, за которым следуют аварийные ситуации EXEC_BAD_ACCESS в объекте B dealloc.
Кто-нибудь знает, почему объект B освобождается после объекта A с OBJC_ASSOCIATION_RETAIN? Выпускаются ли связанные объекты после освобождения? Получают ли они автореализацию? Кто-нибудь знает способ изменить это поведение?
Я пытаюсь добавить некоторые вещи в класс по категориям, поэтому я не могу переопределить какие-либо существующие методы (включая dealloc), и я не особо хочу возиться с swizzling. Мне нужен способ де-ассоциировать и освободить объект B до того, как объект A будет освобожден.
EDIT. Вот код, который я пытаюсь получить. Если связанные объекты были выпущены до того, как UIImageView будет полностью освобожден, все это будет работать. Единственное решение, которое я вижу, - это swizzle в моем собственном методе dealloc и swizzle назад оригинал, чтобы вызвать его. Это становится действительно грязным, хотя.
Точка класса ZSPropertyWatcher заключается в том, что KVO требует стандартного метода обратного вызова, и я не хочу заменять UIImageView, если он сам использует.
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
