Override a method via ObjC Category and call the default implementation?
Asked Answered
T

4

48

When using categories, you can override implementation methods with your own like so:

// Base Class 
@interface ClassA : NSObject 
- (NSString *) myMethod;
@end
@implementation ClassA
- (NSString*) myMethod { return @"A"; }
@end

//Category
@interface ClassA (CategoryB) 
- (NSString *) myMethod;
@end
@implementation ClassA (CategoryB)
- (NSString*) myMethod { return @"B"; }
@end

Calling the method "myMethod" after including the category nets the result "B".

What is the easiest way for the Category implementation of myMethod to call the original Class A myMethod? As near as I can figure out, you'd have to use the low level calls to get the original method hook for Class A and call that, but it seemed like there would be a syntactically easier way to do this.

Tondatone answered 6/7, 2009 at 4:53 Comment(0)
G
37

If you want a hackish way to do this that involves mucking with the objective-c runtime you can always use method swizzling (insert standard disclaimers here.) It will allow you to store the different methods as arbitrariliy named selectors, then swap them in at runtime as you need them.

Gabel answered 6/7, 2009 at 5:15 Comment(3)
It's not necessarily a 'hackish' way. The Objective-C runtime is there for a reason. It is what makes Objective-C superior to other compiled languages.Farron
Based on my experience, cutting to the chase, you want to use JRSwizzle, as mentioned in the above link. It's been bulletproof for me.Froufrou
Link to JRSwizzle code: github.com/rentzsch/jrswizzle … I've come to love this, method swizzling is VERY powerful whenever you want to tweak a 3rd party library to do as you please!Qp
B
20

From comp.lang.objective-C FAQ listing: "What if multiple categories implement the same method? Then the fabric of the Universe as we know it ceases to exist. Actually, that's not quite true, but certainly some problems will be caused. When a category implements a method which has already appeared in a class (whether through another category, or the class' primary @implementation), that category's definition overwrites the definition which was previously present. The original definition can no longer be reached by the Objective-C code. Note that if two categories overwrite the same method then whichever was loaded last "wins", which may not be possible to predict before the code is launched."

From developer.apple.com: "When a category overrides an inherited method, the method in the category can, as usual, invoke the inherited implementation via a message to super. However, if a category overrides a method that already existed in the category's class, there is no way to invoke the original implementation"

Belgae answered 6/7, 2009 at 5:4 Comment(5)
Yep, this really isn't supported by the language.Paddock
I thank you for the comment, but I'm looking for how it can be done (because I know it can be, just not easily) not how it is impossible...Tondatone
I think that super should resolve to the original object. After all, a category is much like a subclass (e.g., you can override methods).Violence
Is it correct to override in Category method declared and implemented in Category of super class?3d
I probably don't need to say, but just in case, for future readers: a category is really not at all like a subclass. super will not resolve to the 'original object'.Brote
P
13

Check out my article about a solution found on the Mac Developer Library: http://codeshaker.blogspot.com/2012/01/calling-original-overridden-method-from.html

Basically, it's the same as the above Method Swizzling with a brief example:

#import <objc/runtime.h>

@implementation Test (Logging)

- (NSUInteger)logLength {
    NSUInteger length = [self logLength];
    NSLog(@"Logging: %d", length);
    return length;
}

+ (void)load {
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(length)), class_getInstanceMethod(self, @selector(logLength)));
}

@end
Penetrant answered 23/1, 2012 at 11:53 Comment(0)
E
1

With the swizzling "helper" methods included in ConciseKit, you actually call the default implementation… weirdly enough.. by calling your SWIZZLED implementation..

You set it up in + (void) load, calling + (BOOL)swizzleMethod:(SEL)originalSelector with:(SEL)anotherSelector in:(Class)klass;, i.e.

[$ swizzleMethod:@selector(oldTired:) 
            with:@selector(swizzledHotness:) in:self.class];

and then in the swizzled method.. let's suppose it returns -(id).. you can do your mischief, or whatever reason you are swizzling in the first place… and then, instead of returning an object, or self, or whatnot..

return [self swizzledHotness:yourSwizzledMethodsArgument];

As explained here…

In this method, it looks like we're calling the same method again, causing and endless recursion. But by the time this line is reached the two method have been swapped. So when we call swizzled_synchronize we're actually calling the original method.

It feels and looks odd, but.. it works. This enables you to add endless embellishments to existing methods, and still "call super" (actually self) and reap the benefits of the original method's handiwork… even without access to the original source.

Endocarditis answered 10/4, 2013 at 16:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.