何时释放相关对象?

Dou*_*ugW 30 iphone cocoa-touch objective-c

我通过关联引用将对象B附加到对象A.对象B观察对象A到KVO的一些属性.

问题是对象B似乎对象A 之后被释放,这意味着它太迟了以将其自身移除为对象A的KVO观察者.我知道这是因为我得到了NSKVODeallocateBreak异常,然后EXEC_BAD_ACCESS在对象B的dealloc中崩溃.

有没有人知道为什么对象B在具有OBJC_ASSOCIATION_RETAIN的对象A之后被释放?解除分配后是否释放相关对象?他们是否被自动释放?有谁知道改变这种行为的方法?

我试图通过类别向类添加一些东西,所以我不能覆盖任何现有的方法(包括dealloc),而且我并不特别想要混乱.在对象A被释放之前,我需要一些方法来解除关联并释放对象B.

编辑 - 这是我正在努力工作的代码.如果在完全取消分配UIImageView之前释放了关联的对象,那么这一切都可以正常工作.我所看到的唯一解决方案是在我自己的dealloc方法中调整,并调回原来以调用它.但这真的很混乱.

ZSPropertyWatcher类的要点是KVO需要一个标准的回调方法,我不想替换UIImageView,以防它自己使用它.

的UIImageView + Loading.h

@interface UIImageView (ZSShowLoading)
@property (nonatomic)   BOOL    showLoadingSpinner;
@end
Run Code Online (Sandbox Code Playgroud)

的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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

Dav*_*ong 64

甚至比你的-dealloc问题还要大:

UIKit不符合KVO标准

没有努力使UIKit类的键值可观察.如果它们中的任何一个,它完全是巧合,并且在Apple的心血来潮中受到打破.是的,我在UIKit框架上为Apple工作.

这意味着您将不得不寻找另一种方法来实现此目的,可能是稍微改变您的视图布局.

  • @DougW为了使KVO能够在UIKit类上工作,他们必须在内部使用他们的setter方法来获得更改通知.很多人没有; 他们只是直接操纵伊娃. (3认同)
  • @Dave DeLong - 这可能是真的,但文档指出NSObject为KVC兼容属性提供"自动"更改通知."frame"和"image"都是符合KVC标准的属性,为什么我们应该期望它们不会受益于NSObject的自动实现呢?这个异常是在某处记录的吗?(参考:自动KVO http://tinyurl.com/6fzsyt5和KVC合规性规则http://tinyurl.com/6y5k4ao).谢谢! (2认同)

jha*_*ott 12

此相关问题的已接受答案解释了对象的释放时间线.结果是:在原始对象的方法完成释放关联对象dealloc.