Here's my solution, using ideas from above, fixing some errors, and making it extensible. You can use this flat out to monitor the superview changing with KVO of some class and its subclasses, it should be plug and play.
Written in C to be simple to understand and fast. You could modify these to be some slick categories of NSObject.
Example use:
add_superview_kvo(UILabel.class);
Then you would have to add your own observer to instances as per normal use.
// simple datatype to neatly manage the various runtime elements
typedef struct {
Class target;
SEL cmd;
Method method;
IMP imp;
} override_t;
// call to initialize an override struct
static override_t
_override_init(Class target, SEL sel) {
BOOL instance = YES; // should be able to handle class methods by changing this
override_t o = {
.target = target,
.cmd = sel,
// note this can return a method from the superclass
.method = instance ? class_getInstanceMethod(target,sel) : class_getClassMethod(target,sel),
.imp = method_getImplementation(o.method)
};
return o;
};
// applies the runtime patch
static void
_override_patch(override_t o, id _Nonnull block) {
IMP imp = imp_implementationWithBlock(block);
// first we try to add the method to the class, if we are
// dealing with an inherited method from a superclass, our
// new method will drop right in
if (!class_addMethod(o.target, o.cmd, imp, method_getTypeEncoding(o.method))){
// this means we got the original method from the
// class we're manipulating, so we just overwrite
// its version
method_setImplementation(o.method,imp);
}
}
// pass the class in here that you want to monitor for superview changes
// if you pass in UIView.class it will monitor all views... this may
// generate unnecessary overhead, so you can focus it on a class that
// you want (you will get that class and all its subclasses)
void
add_superview_kvo(Class target)
{
NSString *keyPath = @"superview";
override_t override = _override_init(target,@selector(willMoveToSuperview:));
_override_patch(override,^void(id _self, UIView *superview) {
[_self willChangeValueForKey:keyPath];
// note that this is the correct way to call an imp, it must be cast
((void(*)(id,SEL,id))override.imp)(_self, override.cmd, superview);
});
override = _override_init(target,@selector(didMoveToSuperview));
_override_patch(override,^void(id _self) {
((void(*)(id,SEL))override.imp)(_self, override.cmd);
[_self didChangeValueForKey:keyPath];
});
}