何时释放相关对象?

时间:2011-05-18 02:39:28

标签: iphone objective-c cocoa-touch

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

问题是对象B似乎在对象A之后被解除分配,这意味着将其自身移除为对象A的KVO观察者为时已晚。我知道这是因为我得到了NSKVODeallocateBreak异常,然后EXEC_BAD_ACCESS在对象B的dealloc中崩溃。

有没有人知道为什么在OBJC_ASSOCIATION_RETAIN的对象A之后取消分配对象B?在解除分配后,关联对象是否会被释放?em>他们是否被自动释放?有没有人知道改变这种行为的方法?

我正在尝试通过类别向类添加一些内容,因此我无法覆盖任何现有方法(包括dealloc),而且我并不特别想要混乱。在对象A被解除分配之前,我需要一些方法来解除关联和释放对象B.

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

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

4 个答案:

答案 0 :(得分:63)

-dealloc问题还要大:

UIKit不符合KVO

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

这意味着你必须找到另一种方法来做到这一点,可能会稍微改变你的视图布局。

答案 1 :(得分:11)

this related question的已接受答案解释了对象的解除分配时间表。结果是:在原始对象的dealloc方法完成后发布关联对象。

答案 2 :(得分:3)

我认为你的情况是这样的:

1)对象A在其保留计数变为0后接收-dealloc调用;

2)关联机制确保在某个时刻释放对象B(与解除分配不同)。

即,我们并不确切知道在哪一点上,但似乎我认为这种语义差异是对象A在对象A之后被释放的原因;对象-dealloc选择器无法识别关联,因此当调用它上一个版本时,将执行-dealloc,并且只有在此之后关联机制才能向对象发送-release B ...

还要看this post

它还声明:

  

现在,当取消分配objectToBeDeallocated时,objectWeWantToBeReleasedWhenThatHappens将自动发送一条释放消息。

我希望这有助于解释您的体验。 至于其余部分,我无法提供太多帮助......

编辑:在DougW的评论之后继续进行如此有趣的猜测......

如果关联机制在释放对象A时被“破坏”,我会看到存在某种循环依赖的风险(继续你的例子)。

  1. 如果从释放方法(而不是dealloc)执行与关联相关的代码,则对于每个版本,您将检查“拥有”对象(对象A)的保留计数是否为1;实际上,在这种情况下,您知道减少其保留计数会触发dealloc,因此在执行此操作之前,您将首先释放关联的对象(示例中的对象B);

  2. 但是如果对象B也在“拥有”第三个对象,比如C?会发生什么情况是在对象B上调用释放时,当对象B保留计数为1时,C将被释放;

  3. 现在,考虑一下对象C“拥有”该序列的第一个对象A的情况。如果,当接收上述版本时,C的保留计数为1,它将首先尝试释放其关联对象,即A;

    1. 但是A的释放计数仍为1,因此另一个版本将被发送到B,其保留计数仍为1;等等,循环播放。
  4. 另一方面,如果您从-dealloc发送释放,则这种循环依赖似乎不可能。

    这是非常做作的,我不确定我的推理是否正确,所以请随意评论......

答案 3 :(得分:1)

对于objc_getAssociatedObject()关联,

OBJC_ASSOCIATION_RETAIN返回一个自动释放的对象。你可以在同一个runloop循环/自动释放池范围内调用它,因为对象A被释放了吗? (您可以通过将关联更改为NONATOMIC来快速测试。