美文网首页
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