24

I'd like to override a method in an Objective C class that I don't have the source to.

I've looked into it, and it appears that Categories should allow me to do this, but I'd like to use the result of the old method in my new method, using super to get the old methods result.

Whenever I try this though, my method gets called, but "super" is nil... Any idea why? I'm doing iPhone development with the XCode 2.2 SDK. I'm definitely working with an instance of a class, and the method of the class is an instance method.

@implementation SampleClass (filePathResolver)
-(NSString*) fullPathFromRelativePath:(NSString*) relPath
{
    NSString *result = [super fullPathFromRelativePath: relPath];

  ... do some stuff with the old result

    return result;
}

Note and clarification: From what I can see in the Apple Docs, it appears to me that this should be allowed?

Categories docs at 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.

Brad Parks
  • 66,836
  • 64
  • 257
  • 336
  • Did you ever find a solution to this? I am trying to modify the text parameter as it is passed into UILabel's setText: method. I do not want to subclass UILabel (huge codebase), so a Category is most likely appropriate. I too think calling super setText would do the trick, if the super object was in fact the UILabel, but then, it would most likely lead to an infinite loop calling setText: defined in the category. So it's clear this is probably an impossible feat for a category. – Jimmy Bouker Nov 18 '14 at 21:32
  • Hey! I never found any other solution than what's posted on this page. Use method swizzling, or a different method in a category ;-( – Brad Parks Nov 19 '14 at 01:17

3 Answers3

29

Categories extend the original class, but they don't subclass it, therefore a call to super doesn't find the method.

What you want is called Method Swizzling. But be aware that your code could break something. There's an article on Theocacao written by Scot Stevenson about Method Swizzling in the old Objective-C runtime, Cocoa with Love by Matt Gallagher has an article about Method Swizzling in the new Objective-C 2.0 runtime and a simple replacement for it.

Alternatively, you could subclass the class and then either use the subclass or use + (void)poseAsClass:(Class)aClass to replace the superclass. Apple writes:

A method defined by a posing class can, through a message to super, incorporate the superclass method it overrides.

Be aware that Apple has deprecated poseAsClass: in Mac OS X 10.5.

animaonline
  • 3,715
  • 5
  • 30
  • 57
Georg Schölly
  • 124,188
  • 49
  • 220
  • 267
  • I'm somewhat disturbed that there is a programming concept called "swizzling". However, it's an interesting concept. – Brian Postow Sep 10 '09 at 13:42
  • 1
    As I added as a clarification above, what does this mean from the apple docs? 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. – Brad Parks Sep 10 '09 at 14:08
  • Only, you really don't want method swizzling unless it is the only possible way to solve the problem. Even then, know that swizzling comes along with a bunch of fragility and maintenance issues. – bbum Sep 10 '09 at 15:13
  • 2
    @ Brad Parks: This means `[super message]` is sent to the superclass, and does not call the overridden method. – Georg Schölly Sep 10 '09 at 15:16
  • 2
    @brian: Apple even does isa swizzling in their implementation of KVO. – Georg Schölly Sep 16 '09 at 10:12
  • Your "Method Swizzling" link is 404'd – Ky - Oct 29 '15 at 15:25
8

If you will be coding against this class, simply rename the selector to something your code can use, and call the original selector on self:

@implementation SampleClass (filePathResolver)
-(NSString*) myFullPathFromRelativePath:(NSString*) relPath
{
    NSString *result = [self fullPathFromRelativePath: relPath];

  ... do some stuff with the old result

    return result;
}

If you want to override the default implementation of this selector for that class, you'll need to use the method swizzling approach.

Jason
  • 28,040
  • 10
  • 64
  • 64
  • yeah, I don't control all the code written against that class... so that doesn't quite work for me, but thanks for the suggestion! – Brad Parks Sep 12 '09 at 10:58
  • 1
    ^ ehm, yes it does. Method swizzling kinda feels like a huge hack and potential future break point of your program - but it should work in exactly that situation, where you don't have control over code written against the class. – n13 Mar 08 '12 at 16:26
  • well what i meant is there's other code that I don't control that calls "fullPathFromRelativePath" on that class. I can't change what the other code does, so swizzling wouldn't help me in this case. – Brad Parks Aug 21 '12 at 18:37
1

Not exactly in category but there is a workaround by adding the method dynamically at runtime. Samuel Défago in his article describes a neat way to create block IMP implementation calling super, his original article can be found here

The relevant code is:

#import <objc/runtime.h>
#import <objc/message.h>

    const char *types = method_getTypeEncoding(class_getInstanceMethod(clazz, selector));
    class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) {
        struct objc_super super = {
            .receiver = self,
            .super_class = class_getSuperclass(clazz)
        };

        id (*objc_msgSendSuper_typed)(struct objc_super *, SEL, va_list) = (void *)&objc_msgSendSuper;
        return objc_msgSendSuper_typed(&super, selector, argp);
    }), types);
Kamil.S
  • 5,205
  • 2
  • 22
  • 51