32

Method swizzling works great for instance methods. Now, I need to swizzle a class method. Any idea how to do it?

Tried this but it doesn't work:

void SwizzleClassMethod(Class c, SEL orig, SEL new) {

Method origMethod = class_getClassMethod(c, orig);
Method newMethod = class_getClassMethod(c, new);

if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
    class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
    method_exchangeImplementations(origMethod, newMethod);
}
Ortwin Gentz
  • 52,648
  • 24
  • 135
  • 213

2 Answers2

57

Turns out, I wasn't far away. This implementation works for me:

void SwizzleClassMethod(Class c, SEL orig, SEL new) {

    Method origMethod = class_getClassMethod(c, orig);
    Method newMethod = class_getClassMethod(c, new);

    c = object_getClass((id)c);

    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}
Ortwin Gentz
  • 52,648
  • 24
  • 135
  • 213
  • Mmm ... Let's say we instead did `Class cc=object_getClass((id)c)`. It's understandable that you need to add the method to `cc`, but why did you take the class method from `cc`? Aren't you supposed to get the class method from `c` ? I'm confused ... – Yuji Jul 16 '10 at 19:24
  • Yuhi, you're right, I've changed the code to do the method resolution on the original class, not the meta class. The old solution worked as well so I didn't really bother. – Ortwin Gentz Jul 20 '10 at 08:00
  • My bad. I mistyped object_getClass into objc_getClass. I'll delete my answer in a second. – Di Wu Dec 01 '14 at 15:00
  • For some reason, it does not work. Using class_getClassMethod with method_setImplementation it works, see https://newrelic.com/blog/best-practices/right-way-to-swizzle I've tried to swizzle +[NSArray array] – Gibezynu Nu May 22 '23 at 09:55
  • @GibezynuNu Swizzling class clusters like NSArray will cause issues. Frameworks rely in a certain functionality, swizzling NSArray will break these assumptions and lead to extremely hard to reproduce bugs. Even if you could do this, please don't. – Ortwin Gentz May 22 '23 at 10:16
  • @OrtwinGentz I've just tried with a simple custom made class. Specifically, class_replaceMethod returns null. – Gibezynu Nu May 22 '23 at 13:36
1

Objective-C swizzling

Objective-C swizzling using category

@interface cA : NSObject {

}
@end

@implementation cA

+(NSString*) cClassFooA {
    return @"class fooA";
}

-(NSString*) cFooA {
    return @"fooA";
}

@end


@interface cB : NSObject {

}
@end

@implementation cB

+(NSString*) cClassFooB {
    return @"class fooB";
}

-(NSString*) cFooB {
    return @"fooB";
}

@end

NSObject+Swizzling.h

#import <Foundation/Foundation.h>

@interface NSObject (Swizzling)

+ (void)cExchangeClassWithCls1:(Class)cls1 Sel1:(SEL)sel1 Cls2:(Class)cls2 Sel2:(SEL)sel2;
+ (void)cExchangeInstanceWithCls1:(Class)cls1 Sel1:(SEL)sel1 Cls2:(Class)cls2 Sel2:(SEL)sel2;

@end

NSObject+Swizzling.m

#import "NSObject+Swizzling.h"
#import <objc/runtime.h>

@implementation NSObject (Swizzling)

+ (void)cExchangeClassWithCls1:(Class)cls1 Sel1:(SEL)sel1 Cls2:(Class)cls2 Sel2:(SEL)sel2 {
    
    Method originalMethod = class_getClassMethod(cls1, sel1);
    Method swizzledMethod = class_getClassMethod(cls2, sel2);
    
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

+ (void)cExchangeInstanceWithCls1:(Class)cls1 Sel1:(SEL)sel1 Cls2:(Class)cls2 Sel2:(SEL)sel2 {
    
    Method originalMethod = class_getInstanceMethod(cls1, sel1);
    Method swizzledMethod = class_getInstanceMethod(cls2, sel2);
    
    method_exchangeImplementations(originalMethod, swizzledMethod);
}
@end

using via Objective-C

- (void)testCExchangeClass {
    [NSObject cExchangeClassWithCls1:[cA class] Sel1:@selector(cClassFooA) Cls2:[cB class] Sel2:@selector(cClassFooB)];
    
    XCTAssertEqual(@"class fooB", [cA cClassFooA]);
}

- (void)testCExchangeInstance {
    [NSObject cExchangeInstanceWithCls1:[cA class] Sel1:@selector(cFooA) Cls2:[cB class] Sel2:@selector(cFooB)];
    
    XCTAssertEqual(@"fooB", [[[cA alloc] init] cFooA]);
}

[Add Swift as an consumer]

using via Swift

func testCExchangeClass() {
    NSObject.cExchangeClass(withCls1: sA.self, sel1: #selector(sA.sClassFooA), cls2: sB.self, sel2: #selector(sB.sClassFooB))
    
    XCTAssertEqual("class fooB", sA.sClassFooA())
}

func testCExchangeInstance() {
    NSObject.cExchangeInstance(withCls1: sA.self, sel1: #selector(sA.sFooA), cls2: sB.self, sel2: #selector(sB.sFooB))
    
    XCTAssertEqual("fooB", sA().sFooA())
}

[Swift swizzling]

yoAlex5
  • 29,217
  • 8
  • 193
  • 205