Debug usage of Objective-C weak properties with KVO
I’ve recently stumbled upon Mike Abdullah’s blog post which has raised an interesting issue. When ARC is used, weak properties can be set to nil
by the runtime. However if you register for KVO notifications using addObserver
method, you won’t get notified of it. This is interesting, as it basically breaks KVO compliance. It is also worth noting that other updates of such properties still result in notifications.
Mike proposed that debugging code can be injected to check for the weak properties used with addObserver
. I’ve decided to implement such code:
#import <objc/runtime.h>
void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL)
{
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
@implementation NSObject (KVOWeakPropertyDebug)
+ (void)load
{
MethodSwizzle(self, @selector(addObserver:forKeyPath:options:context:), @selector(_addObserver:forKeyPath:options:context:));
}
- (BOOL)_isWeak:(NSString *)keyPath
{
// TODO: Support complex keyPath variants
objc_property_t property = class_getProperty(self.class, keyPath.UTF8String);
if (property) {
return property_getAttributes(property)[3] == 'W';
}
return NO;
}
- (void)_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
if ([self _isWeak:keyPath]) {
NSLog(@"WARNING: observing '%@' property, which is weak and so isn't fully KVO-compliant", keyPath);
}
[self _addObserver:observer forKeyPath:keyPath options:options context:context];
}
@end
Basically it is a category on NSObject
that replaces addObserver
method with own implementation using method swizzling. It checks whether observed key corresponds to weak property and logs a warning in such case, then just calls original implementation.
For convenience grab complete code as Gist.