objective-cinitializationmethod-swizzling

How to swizzle initialization method?


I have one class MyOldController with init method

    -(instancetype) initWithMyController: (MyController *) myController {
    if((self = [self init])) {
        _myController = myController;
    }
    return self;
}

I want swizzle this initialization method to another and this my swizzle code

@implementation MyOldController(Swizzle)

+ (void)load {
    [MyOldController swizzleMethods];
}

+ (void)swizzleMethods {
    method_exchangeImplementations(class_getInstanceMethod(self, @selector(initWithMyController)), class_getInstanceMethod(self, @selector(swizzle_ initWithMyController)));
}

I try write this

-(instancetype) swizzle_initWithMyController: (MyController *) myController {
    if((self = [self init])) {
        _myController = myController;
    }
    return self;
}

But it drops error enter image description here

Then I renamed init method to this and updated (void)swizzleMethods

-(instancetype) initWithMyController_swizzle: (MyController *) myController {
    if((self = [self init])) {
        _myController = myController;
    }
    return self;
}

Error message disappeared but swizzle doesn't works. It just calls old initialization method, not my new.

Which point i missed? Is swizzling of initialization method have some special way to do it?


Solution

  • (Starting with the required caveat: this is incredibly dangerous and should never be used in production code. Swizzling initializers is particularly dangerous given designated initializer chaining, and should definitely never be done for anything but exploration and debugging without first confirming the implementation of the swizzled initializer. OK, got that out of the way.)

    I can't reproduce your issue. And initializer should always start with with init, so your second approach is correct. I suspect you've just made a small mistake, perhaps in your @selector (which has a typo in your question, which suggests maybe there's a mistake in your actual code). Here is code that does what you're describing.

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    @interface MyOldController: NSObject
    - (instancetype)initWithInt:(NSInteger)x
    @end
    
    @implementation MyOldController
    - (instancetype)initWithInt:(NSInteger)x
    {
        self = [super init];
        if (self) {
            NSLog(@"init");
        }
        return self;
    }
    @end
    
    @implementation MyOldController(Swizzle)
    
    + (void)load {
        [MyOldController swizzleMethods];
    }
    
    + (void)swizzleMethods {
        method_exchangeImplementations(class_getInstanceMethod(self, @selector(initWithInt:)), class_getInstanceMethod(self, @selector(initWithInt_swizzle:)));
    }
    
    - (instancetype)initWithInt_swizzle:(NSInteger)x
    {
        self = [super init];
        if (self) {
            NSLog(@"init_swizzle");
        }
        return self;
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MyOldController *controller = [[MyOldController alloc] initWithInt:1];
            NSLog(@"%@", controller);
        }
        return 0;
    }
    

    This prints, as expected:

    2018-06-21 12:23:14.431936-0400 test[30981:401466] init_swizzle
    2018-06-21 12:23:14.432172-0400 test[30981:401466] <MyOldController: 0x10051ee10>