iOS method swizzling

221 阅读1分钟

这篇博文是我的另一篇 Aspects源码剖析中的一部分,考虑到这部分内容相对独立,单独成篇以便查询。 在Objective-C中调用一个方法,其实是向一个对象发送消息。每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向Method具体的实现。

selector-imp.png
通过 method swizzling这种黑科技,你可以改变selector和方法实现的映射关系。
swizzled-imp
此时当执行[objc selectorC]时,实际调用的是 IMPn指针指向的函数。

具体实现代码如下:

代码来源: https://github.com/hejunm/iOS-Tools

@implementation HJMSwizzleTools:NSObject
+ (void)hjm_swizzleWithClass:(Class)processedClass originalSelector:(SEL)originSelector swizzleSelector:(SEL)swizzlSelector{
    
    Method originMethod = class_getInstanceMethod(processedClass, originSelector);
    Method swizzleMethod = class_getInstanceMethod(processedClass, swizzlSelector);
    
    //当processedClass实现originSelector时,didAddMethod返回false,否则返回true. 如果当前类没有实现originSelector而父类实现了,这是直接使用method_exchangeImplementations会swizzle父类的originSelector。这样会出现很大的问题。
    BOOL didAddMethod = class_addMethod(processedClass, originSelector, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    
    if (didAddMethod) {
        class_replaceMethod(processedClass, swizzlSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
    }else{
        method_exchangeImplementations(originMethod, swizzleMethod);
    }
}
@end

可以这样使用

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [HJMSwizzleTools hjm_swizzleWithClass:self originalSelector:@selector(viewDidLoad) swizzleSelector:@selector(swizzleViewDidLoad)];
    });
}

//被替换了。。
- (void)viewDidLoad {
    [super viewDidLoad];
}

//现在系统会调用这个方法
- (void)swizzleViewDidLoad {
    NSLog(@"do something");
}