简体   繁体   English

何时释放相关对象?

[英]When does an associated object get released?

I'm attaching object B via associative reference to object A. Object B observes some properties of object A through KVO. 我通过关联引用将对象B附加到对象A.对象B观察对象A到KVO的一些属性。

The problem is that object B seems to be deallocated after object A, meaning its too late to remove itself as a KVO observer of object A. I know this because I'm getting NSKVODeallocateBreak exceptions, followed by EXEC_BAD_ACCESS crashes in object B's dealloc. 问题是对象B似乎对象A 之后被释放,这意味着它太迟了以将其自身移除为对象A的KVO观察者。我知道这是因为我得到NSKVODeallocateBreak异常,然后EXEC_BAD_ACCESS在对象B的dealloc中崩溃。

Does anyone know why object B is deallocated after object A with OBJC_ASSOCIATION_RETAIN? 有没有人知道为什么对象B在具有OBJC_ASSOCIATION_RETAIN的对象A之后被释放? Do associated objects get released after deallocation? 解除分配后是否释放相关对象? Do they get autoreleased? 他们是否被自动释放? Does anyone know of a way to alter this behavior? 有谁知道改变这种行为的方法?

I'm trying to add some things to a class through categories, so I can't override any existing methods (including dealloc), and I don't particularly want to mess with swizzling. 我试图通过类别向类添加一些东西,所以我不能覆盖任何现有的方法(包括dealloc),而且我并不特别想要混乱。 I need some way to de-associate and release object B before object A gets deallocated. 在对象A被释放之前,我需要一些方法来解除关联并释放对象B.

EDIT - Here is the code I'm trying to get working. 编辑 - 这是我正在努力工作的代码。 If the associated objects were released prior to UIImageView being completely deallocated, this would all work. 如果在完全取消分配UIImageView之前释放了关联的对象,那么这一切都可以正常工作。 The only solution I'm seeing is to swizzle in my own dealloc method, and swizzle back the original in order to call up to it. 我所看到的唯一解决方案是在我自己的dealloc方法中调整,并调回原来以调用它。 That gets really messy though. 但这真的很混乱。

The point of the ZSPropertyWatcher class is that KVO requires a standard callback method, and I don't want to replace UIImageView's, in case it uses one itself. ZSPropertyWatcher类的要点是KVO需要一个标准的回调方法,我不想替换UIImageView,以防它自己使用它。

UIImageView+Loading.h 的UIImageView + Loading.h

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

UIImageView+Loading.m 的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 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 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

Even larger than your -dealloc issue is this: 甚至比你的-dealloc问题还要大:

UIKit is not KVO-compliant UIKit不符合KVO标准

No effort has been made to make UIKit classes key-value observable. 没有努力使UIKit类的键值可观察。 If any of them are , it is entirely coincidental and is subject to break at Apple's whim. 如果它们中的任何一个,它完全是巧合,并且在Apple的心血来潮中受到打破。 And yes, I work for Apple on the UIKit framework. 是的,我在UIKit框架上为Apple工作。

This means that you're going to have to find another way to do this, probably by changing your view layouting slightly. 这意味着您将不得不寻找另一种方法来实现此目的,可能是稍微改变您的视图布局。

The accepted answer to this related question explains the deallocation timeline of objects. 此相关问题的已接受答案解释了对象的释放时间线。 The upshot is: Associated objects are released after the dealloc method of the original object has finished. 结果是:在原始对象的dealloc方法完成释放关联对象。

what i think is happening in your case is this: 我认为你的情况是这样的:

1) object A receives the -dealloc call, after its retain count has gone to 0; 1)对象A在其保留计数变为0后接收-dealloc调用;

2) the association mechanism ensures that object B gets released (which is different from deallocated) at some point as a consequence. 2)关联机制确保在某个时刻释放对象B(与解除分配不同)。

ie, we don't know exactly at which point, but it seems likely to me that this kind of semantic difference is the cause of object B being deallocated after object A; 也就是说,我们并不确切知道在哪一点,但似乎我认为这种语义差异是对象A在对象A之后被解除分配的原因; object A -dealloc selector cannot be aware of the association, so when the last release on it is called, -dealloc is executed, and only after that the association mechanism can send a -release to object B... 对象-dealloc选择器无法-dealloc关联,因此当调用它的最后一个版本时,执行-dealloc ,并且只有在此之后关联机制才能向对象B发送-release ...

have also a look at this post . 还看看这篇文章

it also states: 它还指出:

Now, when objectToBeDeallocated is deallocated, objectWeWantToBeReleasedWhenThatHappens will be sent a -release message automatically. 现在,当取消分配objectToBeDeallocated时,objectWeWantToBeReleasedWhenThatHappens将自动发送一个-release消息。

I hope this helps explaining what you are experiencing. 我希望这有助于解释您的体验。 As to the rest, I cannot be of much help... 至于其余的,我帮不了多少......

EDIT: just to keep on with such an interesting speculation after the comment by DougW... 编辑:在DougW的评论之后继续进行如此有趣的猜测......

I see the risk of having a sort of cyclic dependency if the association mechanism were "broken" when releasing object A (to keep going with your example). 如果关联机制在释放对象A时被“破坏”(继续你的例子),我会看到存在一种循环依赖的风险。

  1. if the association-related code were executed from the release method (instead of dealloc), for each release you would check if the "owning" object (object A) has a retain count of 1; 如果从释放方法(而不是dealloc)执行与关联相关的代码,则对于每个版本,您将检查“拥有”对象(对象A)是否具有保留计数1; in fact, in such case you know that decreasing its retain count would trigger dealloc, so before doing that, you would first release the associated object (object B in your example); 实际上,在这种情况下,你知道减少它的保留计数会触发dealloc,所以在这之前,你首先要释放相关的对象(你的例子中的对象B);

  2. but what would happen in case object B were also at its turn "owning" a third object, say it C? 但是如果对象B又“拥有”了第三个对象,比如C,会发生什么? what would happen is that at the time release is called on object B, when object B retain count is 1, C would be released; 会发生的情况是,在对象B上调用释放时,当对象B保留计数为1时,C将被释放;

  3. now, consider the case that object C were "owning" the very first one of this sequence, object A. if, when receiving the release above, C had a retain count of 1, it would first try and release its associated object, which is A; 现在,考虑对象C“拥有”该序列的第一个对象A的情况。如果,当接收上述版本时,C的保留计数为1,它将首先尝试释放其关联对象,是A;

    1. but the release count of A is still 1, so another release would be sent to B, which still has a retain count of 1; 但是A的释放次数仍为1,因此另一个版本将发送给B,其保留计数仍为1; and so on, in a loop. 等等,循环播放。

If you, on the other hand, send the release from the -dealloc such cyclic dependency does not seem possible. 另一方面,如果您从-dealloc发送释放,则这种循环依赖似乎不可能。

It's pretty contrived and I am not sure that my reasoning is right, so feel free to comment on it... 这是非常做作的,我不确定我的推理是否正确,所以随时评论它...

objc_getAssociatedObject() for an OBJC_ASSOCIATION_RETAIN association returns an autoreleased object. objc_getAssociatedObject()用于OBJC_ASSOCIATION_RETAIN关联返回一个自动释放的对象。 Might you be calling it earlier in the same runloop cycle / autorelease pool scope as object A is deallocated? 你可以在同一个runloop循环/自动释放池范围内调用它,因为对象A被释放了吗? (You can probably test this quickly by changing the association to NONATOMIC ). (您可以通过将关联更改为NONATOMIC来快速测试)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM