Skip to content

Instantly share code, notes, and snippets.

@hollance
Created July 18, 2012 21:01
Show Gist options
  • Save hollance/3138865 to your computer and use it in GitHub Desktop.
Save hollance/3138865 to your computer and use it in GitHub Desktop.
Overriding methods without making a subclass
@interface NSObject (MHOverride)
/*
* Dynamically overrides the specified method on this particular instance.
*
* The block's parameters and return type must match those of the method you
* are overriding. However, the first parameter is always "id _self", which
* points to the object itself.
*
* You do have to cast the block's type to (__bridge void *), e.g.:
*
* [self mh_overrideSelector:@selector(viewDidAppear:)
* withBlock:(__bridge void *)^(id _self, BOOL animated) { ... }];
*/
- (BOOL)mh_overrideSelector:(SEL)selector withBlock:(void *)block;
/*
* To call super from the overridden method, do the following:
*
* SEL sel = @selector(viewDidAppear:);
* void (*superIMP)(id, SEL, BOOL) = [_self mh_superForSelector:sel];
* superIMP(_self, sel, animated);
*
* This first gets a function pointer to the super method and then you call it.
*/
- (void *)mh_superForSelector:(SEL)selector;
@end
#import <objc/runtime.h>
#import "NSObject+MHOverride.h"
// Whether to override the -class method to return the old class name
// rather than the one from the override subclass.
#define OVERRIDE_CLASS_METHOD 1
#if OVERRIDE_CLASS_METHOD
static Class OverrideClass(id self, SEL _cmd)
{
NSString *className = [NSString stringWithUTF8String:object_getClassName(self)];
NSString *prefix = [NSString stringWithFormat:@"MHOverride_%p_", self];
if ([className hasPrefix:prefix])
{
className = [className substringFromIndex:[prefix length]];
}
return objc_getClass([className UTF8String]);
}
#endif
@implementation NSObject (MHOverride)
- (BOOL)mh_overrideSelector:(SEL)selector withBlock:(void *)block
{
Class selfClass = [self class];
Class subclass = nil;
NSString *prefix = [NSString stringWithFormat:@"MHOverride_%p_", self];
#if OVERRIDE_CLASS_METHOD
NSString *className = [NSString stringWithUTF8String:object_getClassName(self)];
#else
NSString *className = NSStringFromClass(selfClass);
#endif
if (![className hasPrefix:prefix])
{
NSString *name = [prefix stringByAppendingString:className];
subclass = objc_allocateClassPair(selfClass, [name UTF8String], 0);
if (subclass == NULL)
{
NSLog(@"Could not create subclass");
return NO;
}
#if OVERRIDE_CLASS_METHOD
if (!class_addMethod(subclass, @selector(class), (IMP)OverrideClass, "#@:"))
{
NSLog(@"Could not add 'class' method to class %@", NSStringFromClass(subclass));
return NO;
}
#endif
objc_registerClassPair(subclass);
object_setClass(self, subclass);
}
else // object already has an override subclass
{
#if OVERRIDE_CLASS_METHOD
subclass = objc_getClass([className UTF8String]);
#else
subclass = selfClass;
#endif
}
Method m = class_getInstanceMethod(selfClass, selector);
if (m == NULL)
{
NSLog(@"Could not find method %@ in class %@", NSStringFromSelector(selector), NSStringFromClass(selfClass));
return NO;
}
// See also: http://www.friday.com/bbum/2011/03/17/ios-4-3-imp_implementationwithblock/
IMP imp = imp_implementationWithBlock(block);
if (!class_addMethod(subclass, selector, imp, method_getTypeEncoding(m)))
{
NSLog(@"Could not add method %@ to class %@", NSStringFromSelector(selector), NSStringFromClass(subclass));
return NO;
}
return YES;
}
- (void *)mh_superForSelector:(SEL)selector
{
#if OVERRIDE_CLASS_METHOD
return [[self class] instanceMethodForSelector:selector];
#else
NSString *prefix = [NSString stringWithFormat:@"MHOverride_%p_", self];
Class theClass = [self class];
while (theClass != nil)
{
NSString *className = NSStringFromClass(theClass);
theClass = [theClass superclass];
if ([className hasPrefix:prefix])
return (void *)[theClass instanceMethodForSelector:selector];
}
NSLog(@"Could not find superclass for %@", NSStringFromSelector(selector));
return NULL;
#endif
}
@end
[self.window.rootViewController mh_overrideSelector:@selector(viewDidAppear:) withBlock:(__bridge void *)^(id _self, BOOL animated)
{
// Call super:
SEL sel = @selector(viewDidAppear:);
void (*superIMP)(id, SEL, BOOL) = [_self mh_superForSelector:sel];
superIMP(_self, sel, animated);
// do your own stuff here
}];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment