Search code examples
iosswiftobjective-cmethod-swizzlingswizzle

Swift + Objective C swizzling class_getInstanceMethod is returning nil


I am using AsyndDisplayKit/Texture where:

https://github.com/TextureGroup/Texture/blob/923901a1cebf0b6c51627aca2a6748dee84af143/Source/ASTextNode.mm#L934

ASTextNode has this function:

+ (CGColorRef)_highlightColorForStyle:(ASTextNodeHighlightStyle)style
{
  return [UIColor colorWithWhite:(style == ASTextNodeHighlightStyleLight ? 0.0 : 1.0) alpha:1.0].CGColor;
}

Unfortunately, it's not exposed. So I am unable to change the highlight color. So I am trying to swizzle it using this:

extension ASTextNode {
    
    @objc func swizzledHighlightColor() -> CGColor {
        return UIColor.red.cgColor
    }
    
    static func swizzle() {
        let originalSelector = NSSelectorFromString("_highlightColorForStyle:")
        
        let swizzledSelector = #selector(ASTextNode.swizzledHighlightColor)
        
        print("originalSelector: \(originalSelector)")
        print("swizzledSelector: \(swizzledSelector)")
        
        let originalMethod = class_getInstanceMethod(self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        
        print("originalMethod: \(String(describing: originalMethod))")
        print("swizzledMethod: \(String(describing: swizzledMethod))")
        
        if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}

Here, method_exchangeImplementations fails because the originalMethod gotten by class_getInstanceMethod is returning nil. The output of above is:

originalSelector: _highlightColorForStyle:
swizzledSelector: swizzledHighlightColor
originalMethod: nil
swizzledMethod: Optional(0x00000001046b5e09)

To debug, I printed this and it does work:

let selectorPerformed = ASTextNode.perform(originalSelector)        
print("selectorPerformed: \(String(describing: selectorPerformed))")

prints:

selectorPerformed: Optional(Swift.Unmanaged<Swift.AnyObject>(_value: <CGColor 0x30333c8c0> [<CGColorSpace 0x303430120> (kCGColorSpaceICCBased; kCGColorSpaceModelMonochrome; Generic Gray Gamma 2.2 Profile; extended range)] ( 1 1 )))

So, the originalSelector is correct. Why's class_getInstanceMethod failing then?

EDIT: I figured out the problem. I posted solution below.


Solution

  • Nvm, I figured out the solution. I was mistakenly using class_getInstanceMethod.

    I needed to use class_getClassMethod instead because it's not a instance method.

    Here's the working solution:

    extension ASTextNode {
    
        @objc static func swizzledHighlightColor() -> CGColor {
            return UIColor.red.cgColor
        }
        
        static func swizzle() {
            let originalSelector = NSSelectorFromString("_highlightColorForStyle:")
            let swizzledSelector = #selector(ASTextNode.swizzledHighlightColor)
            
            print("originalSelector: \(originalSelector)")
            print("swizzledSelector: \(swizzledSelector)")
            
            let originalMethod = class_getClassMethod(self, originalSelector)
            let swizzledMethod = class_getClassMethod(self, swizzledSelector)
            
            print("originalMethod: \(String(describing: originalMethod))")
            print("swizzledMethod: \(String(describing: swizzledMethod))")
            
            if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
            
            let selectorPerformed = ASTextNode.perform(originalSelector)
            print("selectorPerformed: \(String(describing: selectorPerformed))")
        }
    }