Search code examples
objective-coverridingobjective-c-category

Call Category's un-overridden method


I am trying to add a condition to NSObject's description method where any object that responds to a method in an optional protocol (PrettyPrinter) will print the result from the protocol method instead of the normal NSObject's description. However, if the object being printer does not respond to the protocol then the description should return how it normally does.

My current attempt at doing this involves writing a category on NSObject that contains this protocol and the overridden description method. However, I am unaware of any way to call a category's un-overridden method.

-(NSString*)description
{
    if ([self respondsToSelector:@selector(prettyPrinter)]) {
        return self.prettyPrinter;
    }else{
        // Would Like to return normal description if does not respond to selector. But
        // can not call super description due to being a category instead of a subclass
        return [super description];
    }
}

Any ideas on ways that I can accomplish this would be greatly appreciated. Thanks!

UPDATE: With a little more searching, it would seem like this may be able to be accomplished through something called swizzling. However, current attempts at this have not yet been successful. Any advise on techniques to use swizzling to accomplish this goal would also be helpful.


Solution

  • As you point out, this is possible with method swizzling. The runtime has functionality to exchange the implementation of two methods.

    #import <objc/runtime.h>
    
    @interface NSObject (PrettyPrinter)
    - (NSString*) prettyPrinter;
    @end
    
    @implementation NSObject (PrettyPrinter)
    
    // This is called when the category is being added
    + (void) load {
      Method method1 = class_getInstanceMethod(self, @selector(description));
      Method method2 = class_getInstanceMethod(self, @selector(swizzledDescription));
    
      // this is what switches the two methods
      method_exchangeImplementations(method1, method2);
    }
    
    // This is what will be executed in place of -description
    - (NSString*) swizzledDescription {
      if( [self respondsToSelector:@selector(prettyPrinter)] ) {
        return [self prettyPrinter];
      }
      else {
        return [self swizzledDescription];
        // The above is not a recursive call, remember the implementation has
        //   been exchanged, this will really execute -description
      }
    }
    
    - (NSString*) prettyPrinter {
      return @"swizzled description";
    }
    
    @end
    

    The -prettyPrinter method can be removed, in which case NSLog will return the value defined by -description.

    Note that this will only swizzle -description, although NSLog might call other methods, such as NSArray's –descriptionWithLocale: