什么时候一个相关的对象被释放?

时间:2022-05-28 09:41:53

I'm attaching object B via associative reference to object A. Object B observes some properties of object A through KVO.

我通过对物体A的联想引用附加物体B,物体B通过KVO观察物体A的一些属性。

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异常,然后是对象B的dealloc中的EXEC_BAD_ACCESS崩溃。

Does anyone know why object B is deallocated after object A with OBJC_ASSOCIATION_RETAIN? Do associated objects get released after deallocation? Do they get autoreleased? Does anyone know of a way to alter this behavior?

有人知道为什么在object A之后用OBJC_ASSOCIATION_RETAIN来释放对象B吗?关联对象在释放后释放吗?他们得到生成吗?有人知道改变这种行为的方法吗?

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. I need some way to de-associate and release object B before object A gets deallocated.

我试图通过类别向类添加一些内容,因此不能覆盖任何现有的方法(包括dealloc),而且我也不想弄乱swizzle。在对象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. 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. That gets really messy though.

编辑——这是我想要的代码。如果关联的对象在UIImageView被完全释放之前被释放,这将会全部工作。我看到的唯一解决方案是使用我自己的dealloc方法,并将原始方法回调以调用它。这真的很麻烦。

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

4 个解决方案

#1


11  

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方法完成之后被释放。

#2


63  

Even larger than your -dealloc issue is this:

比你的-dealloc问题更重要的是:

UIKit is not KVO-compliant

UIKit不是KVO-compliant

No effort has been made to make UIKit classes key-value observable. If any of them are, it is entirely coincidental and is subject to break at Apple's whim. And yes, I work for Apple on the UIKit framework.

还没有做出任何努力使UIKit类的键值是可观察的。如果有的话,这完全是巧合,而且会在苹果的心血来潮中崩溃。是的,我在UIKit框架上为苹果公司工作。

This means that you're going to have to find another way to do this, probably by changing your view layouting slightly.

这意味着您将不得不寻找另一种方法来实现这一点,可能是通过稍微更改视图layouting。

#3


3  

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在其retain count变为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在某个时刻被释放(这与释放不同)。

i.e., 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; 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...

即。,我们不知道确切的时间点,但在我看来,这种语义上的差异可能是物体B在物体A之后被释放的原因;对象A -dealloc选择器不知道关联,因此,当它上的最后一个释放被调用时,-dealloc将被执行,并且只有在此之后,关联机制才能将一个-release发送到对象B…

have also a look at this post.

你也可以看看这篇文章。

it also states:

它还规定:

Now, when objectToBeDeallocated is deallocated, objectWeWantToBeReleasedWhenThatHappens will be sent a -release message automatically.

现在,当objecttobedeallocate被释放时,objectwewanttobereleasedwhen将自动发送-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...

编辑:在杜父的评论之后,继续进行这样一个有趣的猜测……

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; 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)执行的,那么对于每个发布,您将检查“拥有”对象(对象A)的retain count是否为1;实际上,在这种情况下,您知道减少保留计数将触发dealloc,因此在此之前,您将首先释放关联的对象(在示例中是对象B);

  2. but what would happen in case object B were also at its turn "owning" a third object, say it 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也拥有第三个物体,比如说C,会发生什么呢?当对象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; and so on, in a loop.
    2. 但是A的释放计数仍然是1,所以另一个释放将被发送给B, B仍然保留计数为1;等等,循环。

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...

这是精心设计的,我不确定我的推理是否正确,所以请随意评论……

#4


1  

objc_getAssociatedObject() for an OBJC_ASSOCIATION_RETAIN association returns an autoreleased object. Might you be calling it earlier in the same runloop cycle / autorelease pool scope as object A is deallocated? (You can probably test this quickly by changing the association to NONATOMIC).

objc_getAssociatedObject()对于OBJC_ASSOCIATION_RETAIN association返回一个autoreleated对象。您是否可能在与对象A解除分配的运行循环/ autorelease池范围相同的早期调用它?(您可以通过将关联更改为NONATOMIC来快速测试这一点)。

#1


11  

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方法完成之后被释放。

#2


63  

Even larger than your -dealloc issue is this:

比你的-dealloc问题更重要的是:

UIKit is not KVO-compliant

UIKit不是KVO-compliant

No effort has been made to make UIKit classes key-value observable. If any of them are, it is entirely coincidental and is subject to break at Apple's whim. And yes, I work for Apple on the UIKit framework.

还没有做出任何努力使UIKit类的键值是可观察的。如果有的话,这完全是巧合,而且会在苹果的心血来潮中崩溃。是的,我在UIKit框架上为苹果公司工作。

This means that you're going to have to find another way to do this, probably by changing your view layouting slightly.

这意味着您将不得不寻找另一种方法来实现这一点,可能是通过稍微更改视图layouting。

#3


3  

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在其retain count变为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在某个时刻被释放(这与释放不同)。

i.e., 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; 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...

即。,我们不知道确切的时间点,但在我看来,这种语义上的差异可能是物体B在物体A之后被释放的原因;对象A -dealloc选择器不知道关联,因此,当它上的最后一个释放被调用时,-dealloc将被执行,并且只有在此之后,关联机制才能将一个-release发送到对象B…

have also a look at this post.

你也可以看看这篇文章。

it also states:

它还规定:

Now, when objectToBeDeallocated is deallocated, objectWeWantToBeReleasedWhenThatHappens will be sent a -release message automatically.

现在,当objecttobedeallocate被释放时,objectwewanttobereleasedwhen将自动发送-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...

编辑:在杜父的评论之后,继续进行这样一个有趣的猜测……

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; 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)执行的,那么对于每个发布,您将检查“拥有”对象(对象A)的retain count是否为1;实际上,在这种情况下,您知道减少保留计数将触发dealloc,因此在此之前,您将首先释放关联的对象(在示例中是对象B);

  2. but what would happen in case object B were also at its turn "owning" a third object, say it 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也拥有第三个物体,比如说C,会发生什么呢?当对象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; and so on, in a loop.
    2. 但是A的释放计数仍然是1,所以另一个释放将被发送给B, B仍然保留计数为1;等等,循环。

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...

这是精心设计的,我不确定我的推理是否正确,所以请随意评论……

#4


1  

objc_getAssociatedObject() for an OBJC_ASSOCIATION_RETAIN association returns an autoreleased object. Might you be calling it earlier in the same runloop cycle / autorelease pool scope as object A is deallocated? (You can probably test this quickly by changing the association to NONATOMIC).

objc_getAssociatedObject()对于OBJC_ASSOCIATION_RETAIN association返回一个autoreleated对象。您是否可能在与对象A解除分配的运行循环/ autorelease池范围相同的早期调用它?(您可以通过将关联更改为NONATOMIC来快速测试这一点)。