美文网首页
iOS 黑魔法之Method-Swizzling

iOS 黑魔法之Method-Swizzling

作者: Johnny_Z | 来源:发表于2020-10-26 00:25 被阅读0次

    Method-Swizzle

    Method Swizziling 是OC运行时给我们的用于交换Method的实现方式(IMP)的能力。
    其用到的核心方法就是method_exchangeImplementations,代码存在于objc-runtime-new.mm

    void method_exchangeImplementations(Method m1, Method m2)
    {
        if (!m1  ||  !m2) return;
    
        mutex_locker_t lock(runtimeLock);
    
        IMP m1_imp = m1->imp;
        m1->imp = m2->imp;
        m2->imp = m1_imp;
    
    
        // RR/AWZ updates are slow because class is unknown
        // Cache updates are slow because class is unknown
        // fixme build list of classes whose Methods are known externally?
    
        flushCaches(nil);
    
        adjustCustomFlagsForMethodChange(nil, m1);
        adjustCustomFlagsForMethodChange(nil, m2);
    }
    

    原理实现图

    method-swizzling.png
    经过上述我们就知道,当经过Method-Swizzling后原本两个方法的实现IMP得到了互换。

    具体使用

    我们准备连个文件.h和两个.m文件;
    LGPerson表示如下

    #import <Foundation/Foundation.h>
    
    @interface LGPerson : NSObject
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    

    LGPerson.m表示如下

    @implementation LGPerson
    - (void)personInstanceMethod{
        NSLog(@"person对象方法:%s",__func__);
    }
    - (void)personClassMethod{
        NSLog(@"person类方法:%s",__func__);
    }
    @end
    

    LGPerson+LG.h表示如下

    #import "LGPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LGPerson (LG)
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    LGPerson+LG.m表示如下

    #import "LGPerson+LG.h"
    #import <objc/runtime.h>
    
    @implementation LGPerson (LG)
    +(void)initialize{
        static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                Class class = [self class];
                
                SEL defaultSelector = @selector(personInstanceMethod);
                SEL swizzledSelector = @selector(MonitoringCallPersonInstanceMethod);
                
                Method defaultMethod = class_getInstanceMethod(class, defaultSelector);
                Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
                
                method_exchangeImplementations(defaultMethod, swizzledMethod);
            });
    }
    
    -(void)MonitoringCallPersonInstanceMethod{
        NSLog(@"现在是MonitoringCallPersonInstanceMethod被调用了哦");
    }
    @end
    

    当我们调用

    LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];
    

    输出了

    现在是MonitoringCallPersonInstanceMethod被调用了哦
    

    是不是现在有了一个大概的认识

    method-swizzling 使用时注意点

    • 1、使用过程中保证一次性,为什么要保持一次呢?如果方法交换两次的时候不就交换回来了吗,所以避免重复交换可通过dispatch_once来实现,大家可以从我的LGPerson+LG.m例子中看出

    我这里没有将方法添加到+load,是因为保持OC对象的懒加载特性,让我们的应用启动变快

    • 2、子类实现的方法交换的时候,被交换的方法来自父类;如果这样就会出问题;要保证交换的方法是来自当前本类
      • 2.1当这种情况发生后,父类的其他子类也会调用实现method-swizzling的方法,这种侵入性太大了。
      • 2.2 当子类中维持父类方法的调用我们会这样写(LGStudent是LGPerson的子类)
    @implementation LGStudent (LG)
    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
               Class class = [self class];
                
                SEL defaultSelector = @selector(personInstanceMethod);
                SEL swizzledSelector = @selector(lg_studentInstanceMethod);
                
                Method defaultMethod = class_getInstanceMethod(class, defaultSelector);
                Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
                
                method_exchangeImplementations(defaultMethod, swizzledMethod);
        });
    }
    
    - (void)lg_studentInstanceMethod{
        [self lg_studentInstanceMethod]; //lg_studentInstanceMethod -/-> personInstanceMethod
        NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__);
    }
    
    @end
    

    那么当父类或者父类的其他子类调用就会出现

    image.png

    为了避免这个问题,一般方式是给类添加方法class_addMethod

    • 如果添加成功,即当前类是没有这个方法的,可以通过class_replaceMethod进行替换,这样就不会修改父类和其他子类
      -如果添加失败,即当前类是存在该方法的,则可以通过method_exchangeImplementations进行方法交换

    一个比较合理的方法交换实现

    + (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{
        
        if (!cls) NSLog(@"传入的交换类不能为空");
        // oriSEL       personInstanceMethod
        // swizzledSEL  lg_studentInstanceMethod
        
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
       
        // 尝试添加你要交换的方法 - lg_studentInstanceMethod
        BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
    
        /**
         personInstanceMethod(sel) - lg_studentInstanceMethod(imp)
         lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp)
         */
        
        if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{ // 自己有
            method_exchangeImplementations(oriMethod, swiMethod);
        }
        
        
    }
    

    Method-Swizzling 使用场景(黑魔法确实是黑魔法)

    1、在逆向中可以对方法下hook
    2、监听方法的调用
    3、面向切换编程AOP(Aspect Oriented Programming),在某个方法调用前,检测参数是否合理,不合理做一些防崩溃处理;常见的是[NSArray objectIndex:],检测数组是否越界,如果越界则直接返回nil,如果没有越界,则正常调用

    相关文章

      网友评论

          本文标题:iOS 黑魔法之Method-Swizzling

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