Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 
like image 972
DougW Avatar asked May 18 '11 02:05

DougW


People also ask

What is an associated object?

Associated Objects is part of Objective-C runtime. It allow you to associate objects at runtime. Simply, you can attach any object to any other object without subclassing.

What method is automatically called to release object?

Before releasing objects, the CLR automatically calls the Finalize method for objects that define a Sub Finalize procedure. The Finalize method can contain code that needs to execute just before an object is destroyed, such as code for closing files and saving state information.

What is an associated object in Java?

An associated object is like an instance variable that you add to running code. You know IVARs you usually put in the header or in curly braces below the @implementation. But there’s one disadvantage, you cannot add them in a category.

Does the last reference to an object release its resources?

If the variable held the last reference to the object, the object's resources were released immediately. In later versions of Visual Basic, while there may be cases in which this procedure is still valuable, performing it never causes the referenced object to release its resources immediately.

How to release resources immediately from an object?

To release resources immediately, use the object's Dispose method, if available. The only time you should set a variable to Nothing is when its lifetime is long relative to the time the garbage collector takes to detect orphaned objects.

What happens when I assign a new object to a self object?

In this instance the self object will get a new associated object anObject and the storage policy will be to retain it atomically on assigning and releasing it when self goes away. The same is true if you associate a different object for the same myKey.


1 Answers

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.

like image 99
Dave DeLong Avatar answered Oct 02 '22 19:10

Dave DeLong