5

I'm following Swift & the Objective-C Runtime, it works for normal methods.

I like to swizzle init method, from my understanding, init is like a class method. So I tried swizzling init as instance and class method. But it does not seem to work

I can make it work using Objective C, just wonder how to make it work in Swift

Excerpted from my gist

dispatch_once(&Static.token) {
            let originalSelector = Selector("init:source:destination:")
            let swizzledSelector = Selector("ftg_init:source:destination:")

            let originalMethod = class_getClassMethod(self, originalSelector)
            let swizzledMethod = class_getClassMethod(self, swizzledSelector)

            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
JAL
  • 41,701
  • 23
  • 172
  • 300
onmyway133
  • 45,645
  • 31
  • 257
  • 263
  • What does your class inherit from? I believe swizzling only work with classes that inherit from `NSObject` or are defined as an objective c class using `@objc`. – Craig Siemens Dec 16 '15 at 17:34
  • @CleverError I linked to my gist in the question, can you take a look – onmyway133 Dec 16 '15 at 17:46

2 Answers2

6

When creating the selector for a method, you should base it off the Obj C method signature since swizzling is done using the Obj C runtime.

So the original selector should be

initWithIdentifier:source:destination:

Now this gets a little weird, since your init method is defined so that the label on the first argument is required (by having identifier twice), the selector you want to use is actually

ftg_initWithIdentifier:source:destination:

The only documentation I could find about it talk about the translation from Obj C to Swift but it looks like the reverse is happening from Swift to Obj C.

Next, init... is an instance method so you'll need to make two changes. You need to change class_getClassMethod to class_getInstanceMethod and you need to remove class from your ft_init... method.

So when all is said and done, your code should look like this (which worked for me)

dispatch_once(&Static.token) {
    let originalSelector = Selector("initWithIdentifier:source:destination:")
    let swizzledSelector = Selector("ftg_initWithIdentifier:source:destination:")

    let originalMethod = class_getInstanceMethod(self, originalSelector)
    let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

    let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

    if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

func ftg_init(identifier identifier: String!,
    source: UIViewController,
    destination: UIViewController) -> UIStoryboardSegue {

    return ftg_init(identifier: identifier,
        source: source,
        destination: destination.ftg_resolve())
}
Craig Siemens
  • 12,942
  • 1
  • 34
  • 51
5

Here is the way of doing it in Swift 4.2 for UIImage initializer:

@objc static func ftg_imageNamed(_ name: String) -> UIImage? {
    ...
}

private static func swizzleInitImplementation() {
    let originalSelector = #selector(UIImage.init(named:))
    let swizzledSelector = #selector(UIImage.ftg_imageNamed(_:))

    guard let originalMethod = class_getClassMethod(self, originalSelector), let swizzledMethod = class_getClassMethod(self, swizzledSelector) else {
        assertionFailure("The methods are not found!")
        return
    }

    let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
    if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}
sgl0v
  • 1,357
  • 11
  • 13
  • I voted this up cause I couldn't figure out `#selector(UIImage.init(named:))` until your post. Thank you! One thing: `Static methods may only be declared on a type` is what I'm getting at global scope. – ScottyBlades Jan 13 '19 at 02:00
  • Also, I'm having trouble casting self to AnyClass as is required by these methods if these methods are supposed to live in a `uiimage` extension... – ScottyBlades Jan 13 '19 at 02:07