How to Swizzle a Class Method on iOS

How to swizzle a class method on iOS?

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);
}

How to swizzle a method of a private class

Managed to get this to work, it's pretty simple actually.

So the way I did it:

  • made a NSObject category: @interface NSObject(PrivateSwizzleCategory)
  • swizzled:

    +(void)load
    {
    Method original, swizzled;

    original = class_getInstanceMethod(objc_getClass("SomePrivateClass"), @selector(somePrivateMethod:));
    swizzled = class_getInstanceMethod(self, @selector(swizzled_somePrivateMethod:));
    method_exchangeImplementations(original, swizzled);
    }
  • To call the original implementation, I had to cast self to NSObject:

    id ret = [(NSObject *)self swizzled_somePrivateMethod:someParam];
  • To access private properties of the private class, I used valueForKey on self:

    id privateProperty = [self valueForKey:@"__privateProperty"];

Everything works!

Swizzling is not working for class methods

Methods instance exist in dispatch table class, but
class methods exist in dispatch table meta_class so
you need use 'meta class' instead self(class).

#import "TNUserDetail.h"
#import <objc/runtime.h>

@implementation TNUserDetail

+ (void)swizzleInstanceSelector:(SEL)originalSelector withNewSelector:(SEL)newSelector {
const char *className = [NSStringFromClass(self) UTF8String];
Class clazz = objc_getMetaClass(className);
Method originalMethod = class_getClassMethod(clazz, originalSelector);
Method newMethod = class_getClassMethod(clazz, newSelector);

BOOL methodAdded = class_addMethod(clazz,
originalSelector,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod));

if (methodAdded) {
class_replaceMethod(clazz,
newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, newMethod);
}
}

+ (void)load {
[super load];
[self swizzleInstanceSelector:@selector(printHello) withNewSelector:@selector(printHelloWorld)];
}

+ (void)printHello {
NSLog(@"Hello");
}

+ (void)printHelloWorld {
NSLog(@"Hello World");
}

@end

and call [TNUserDetail printHello]; print 'Hello World'

But your swizzling affects the entire project. For this case I recommendation use partial mocks (OCMock)

how to swizzle method of class with some customized method through extension

Refer to swift method_exchangeImplementations not work

By adding the dynamic declaration modifier, the swizzling happens properly. Without this, the call to method_exchangeImplementations() does not have the intended effect. See https://swiftunboxed.com/interop/objc-dynamic/ for more information about dynamic dispatch.

So like this:

@objc dynamic func name() {
print("this is class A")
}

ios swizzle better understanding

Method swizzling is used for overriding original methods with the custom one at runtime. So you can exchange almost any method, (including the private apple implemented ones) with the custom one you wrote.

So imagine there is class named Parent with a method named A and you exchange it with B somewhere before it been called like inside load method. From now on every sub class off 'Parent' will use B except the original 'A' method. But what if you override A in a child class? As Inheritance definition, objects will call their own methods and if they haven't implement it, they use their suprclass's method. So what if you want the parent implementation? Thats where super comes in.

Conclusion

  • If you override a method, the super class (or the custom exchanged method in superclass) method will not getting called
  • If you want the parent implementation, you have to use super keyword to access it

And in this questions case:

  • Overriding a method in sub class without calling super means you just override swizzled method and it will not getting called.

Hope it helps

How to swizzle init method of NSURLConnection class


extension NSURLConnection{
public override class func initialize() {
struct Static {
static var token: dispatch_once_t = 0
}

if self !== NSURLConnection.self {
return
}

dispatch_once(&Static.token) {
let originalSelector = Selector("initWithRequest:delegate:startImmediately:")
let swizzledSelector = Selector("initWithTest:delegate:startImmediately:")

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)
}
}
}

// MARK: - Method Swizzling
convenience init(test: NSURLRequest, delegate: AnyObject?, startImmediately: Bool){
print("Inside Swizzled Method")
self.init()
}
}

Swift - method swizzling

Extend your class:

extension YourClassName {
static let classInit: () -> () = {
let originalSelector = #selector(originalFunction)
let swizzledSelector = #selector(swizzledFunction)
swizzle(YourClassName.self, originalSelector, swizzledSelector)
}

@objc func swizzledFunction() {
//Your new implementation
}
}

Your class (YourClassName) should inherit from NSObject and originalSelector should be a dynamic method.

swizzle is a closure that exchanges the implementations:

private let swizzle: (AnyClass, Selector, Selector) -> () = { fromClass, originalSelector, swizzledSelector in
guard
let originalMethod = class_getInstanceMethod(fromClass, originalSelector),
let swizzledMethod = class_getInstanceMethod(fromClass, swizzledSelector)
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}

You could define swizzle in the class where you are going to do the swizzling. For example the AppDelegate class definition. And then do the swizzling in AppDelegate.init():

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
override init() {
super.init()
YourClassName.classInit()
}
}

Example

Here is a concrete example of swizzling, it exchanges the implementation of two methods that take one argument:

class Example: NSObject {
@objc dynamic func sayHi(to name: String) {
print("Hi", name)
}
}

extension Example {
public class func swizzleMethod() {
guard
let originalMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHi(to:))),
let swizzledMethod = class_getInstanceMethod(Example.self, #selector(Example.sayHello(to:)))
else { return }
method_exchangeImplementations(originalMethod, swizzledMethod)
}
@objc func sayHello(to name: String) {
print("Hello", name)
}
}

Example.swizzleMethod()

let a = Example()
a.sayHi(to: "Nitish") //Hello Nitish


Related Topics



Leave a reply



Submit