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
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.
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With