Search code examples
objective-cintrospectionobjective-c-runtime

Replacing a class method in objective C and calling original implementation


I am trying to replace a class method of UIImage with my own implementation.

In some cases, my implementation might just want to call the original UIImage implementation.

Here is my code

#import "UIImage+SkinnedImage.h"
#import <objc/runtime.h>

@interface UIImage (SkinnedImagePrivate)

+ (UIImage *)originalImageNamed:(NSString*)name;

@end


@implementation UIImage (SkinnedImage)

+ (void)allowSkinning {
    Method imageNamedMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
    IMP originalImageNamedIMP = class_getMethodImplementation_stret([UIImage class], @selector(imageNamed:));
    Method swizzled = class_getClassMethod([UIImage class], @selector(skinnedImageNamed:));
    method_exchangeImplementations(imageNamedMethod, swizzled);
    const char *signatureEnconding = method_getTypeEncoding(imageNamedMethod);
    class_addMethod([UIImage class], @selector(originalImageNamed:), originalImageNamedIMP, signatureEnconding);

}


+ (UIImage *)skinnedImageNamed:(NSString *)name {
    //XXX 
    return [UIImage originalImageNamed:name];
}

@end
  1. + (void)allowSkinning is called.
  2. + (UIImage *)skinnedImageNamed:(NSString *)name is called instead of + (UIImage *)imageNamed:(NSString *)name;
  3. return [UIImage originalImageNamed:name]; crash with error:

reason: '+[UIImage originalImageNamed:]: unrecognized selector sent to class

Why did class_addMethod([UIImage class], @selector(originalImageNamed:), originalImageNamedIMP, signatureEnconding); failed to provide me with an implementation ?

Please note that the function class_addMethod returns true


Solution

  • This line adds an instance method, -originalImageNamed:, to UIImage:

    class_addMethod([UIImage class], @selector(originalImageNamed:), originalImageNamedIMP, signatureEnconding);
    

    You can change it to:

    class_addMethod(object_getClass([UIImage class]), @selector(originalImageNamed:), originalImageNamedIMP, signatureEnconding);
    

    Or, equivalently:

    class_addMethod(objc_getMetaClass("UIImage"), @selector(originalImageNamed:), originalImageNamedIMP, signatureEnconding);
    

    A class method is an instance method on the class's meta-class. So, adding an instance method to that meta-class adds a class method to the class.

    There's a similar problem with your call to class_getMethodImplementation_stret(). That function gets the implementation of an instance method. Therefore, if you want to get a class method, you have to pass the class's meta-class:

    IMP originalImageNamedIMP = class_getMethodImplementation(object_getClass([UIImage class]), @selector(imageNamed:));
    

    Or, since you already have the corresponding Method, you could do:

    IMP originalImageNamedIMP = method_getImplementation(imageNamedMethod);
    

    By the way, why are you using class_getMethodImplementation_stret()? +imageNamed: does not return a structure.