美文网首页
OC底层原理探索—Method Swizzling方法交换

OC底层原理探索—Method Swizzling方法交换

作者: 十年开发初学者 | 来源:发表于2021-07-26 15:51 被阅读0次

sel 和 imp

在讲Method Swizzling前先讲一下sel 和 imp

  • sel:方法编号,在read_images期间就就编译进入内存
  • imp:函数指针地址,寻找imp就是寻找函数的过程

方法交换原理

每一个方法都有一个sel和imp方法编号SEL通过Dispatch table表寻找对应的imp,,Dispatch table是一张SEL和IMP的对应表。

  • 方法编号SEL方法实现IMP的对应关系

    image.png
  • 方法交换后


    image.png
  • oriSEL ->swiImp

  • swiSEL->oriIMP

    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    method_exchangeImplementations(oriMethod, swiMethod);

在方法交换时,一定要将上述代码,放进单例中进行,否则一不注意多次调用方法,才发现交换了个寂寞

案例分析

常规Method Swizzling

创建一个Person

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *per = [[Person alloc] init];
    [per personInstance];
}

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
         NSLog(@"方法交换---:%s", __func__);
         Method oriIMP = class_getInstanceMethod(self, @selector(personInstance));
         Method swiIMP = class_getInstanceMethod(self, @selector(personInstance2));
         method_exchangeImplementations(oriIMP, swiIMP);
     });

}

- (void)personInstance{
    NSLog(@"Person类:%s",__func__);
}

- (void)personInstance2{
     NSLog(@"Person类:%s",__func__);
}

image.png

看打印方法已经交换

  • 接下来我们在personInstance方法中调用personInstance看下是否会发生递归
- (void)personInstance2{
    [self personInstance2];
    NSLog(@"Person类:%s",__func__);
}
image.png

我们发现并没有发生崩溃,而是先打印的personInstance,在打印personInstance2

分析:是因为在方法交换时personInstanceimp指向了personInstance2,personInstance2imp指向了personInstance,所以当我们调用 personInstance2时,实际上调用的是personInstance

image.png

交换父类方法

我们在创建一个student类,继承自Person

- (void)viewDidLoad {
    [super viewDidLoad];

    Student *stu = [[Student alloc] init];
    [stu personInstance];
    
    Person *per = [[Person alloc] init];
    [per personInstance];
}

@implementation Person


- (void)personInstance{

    NSLog(@"Person类:%s",__func__);
}


@end

@implementation Student

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method oriMethod = class_getInstanceMethod(self, @selector(personInstance));
        Method swMethod = class_getInstanceMethod(self, @selector(studentInstance));
        method_exchangeImplementations(oriMethod, swMethod);
    });
}
- (void)studentInstance{
    NSLog(@"Student类:%s",__func__);
}

@end
image.png
这个时候我们发现都打印studentInstance

接下来我们在studentInstance方法中,添加一行[self studentInstance];

@implementation Student

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method oriMethod = class_getInstanceMethod(self, @selector(personInstance));
        Method swMethod = class_getInstanceMethod(self, @selector(studentInstance));
        method_exchangeImplementations(oriMethod, swMethod);
    });
}
- (void)studentInstance{
    [self studentInstance];
    NSLog(@"Student类:%s",__func__);
}

@end
image.png

原因:是因为子类,父类都调用personInstance 方法personInstance其实已经指向了studentInstance,但是在studentInstance方法中添加[self studentInstance],此时父类中没有studentInstance方法,所以报找不到该方法的错误

方法交换封装

+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
    
    if (!cls) NSLog(@"传入的交换类不能为空");
    
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!oriMethod) {
        // 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }
    
    // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
    // 交换自己没有实现的方法:
    //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
    //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL
    //oriSEL:personInstanceMethod

    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    
}

相关文章

网友评论

      本文标题:OC底层原理探索—Method Swizzling方法交换

      本文链接:https://www.haomeiwen.com/subject/zokomltx.html