Skip to main content

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.





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.





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?





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.





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.





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.





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




Comments

  1. Even larger than your -dealloc issue is this:

    UIKit is not 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.

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

    ReplyDelete
  2. 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;

    2) the association mechanism ensures that object B gets released (which is different from deallocated) at some point as a consequence.

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

    have also a look at this post.

    it also states:


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


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


    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);
    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;
    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;


    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.



    If you, on the other hand, send the release from the -dealloc such cyclic dependency does not seem possible.

    It's pretty contrived and I am not sure that my reasoning is right, so feel free to comment on it...

    ReplyDelete
  3. 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).

    ReplyDelete

Post a Comment

Popular posts from this blog

Slow Android emulator

I have a 2.67 GHz Celeron processor, 1.21 GB of RAM on a x86 Windows XP Professional machine. My understanding is that the Android emulator should start fairly quickly on such a machine, but for me it does not. I have followed all instructions in setting up the IDE, SDKs, JDKs and such and have had some success in staring the emulator quickly but is very particulary. How can I, if possible, fix this problem?

CCNA 3 Final Exam => latest version

1 . Which security protocol or measure would provide the greatest protection for a wireless LAN? WPA2 cloaking SSIDs shared WEP key MAC address filtering   2 . Refer to the exhibit. All trunk links are operational and all VLANs are allowed on all trunk links. An ARP request is sent by computer 5. Which device or devices will receive this message? only computer 4 computer 3 and RTR-A computer 4 and RTR-A computer 1, computer 2, computer 4, and RTR-A computer 1, computer 2, computer 3, computer 4, and RTR-A all of the computers and the router   3 . Refer to the exhibit. Hosts A and B, connected to hub HB1, attempt to transmit a frame at the same time but a collision occurs. Which hosts will receive the collision jamming signal? only hosts A and B only hosts A, B, and C only hosts A, B, C, and D only hosts A, B, C, and E   4 . Refer to the exhibit. Router RA receives a packet with a source address of 192.168.1.65 and a destination address of 192.168.1.161...