iOS中的 Method_Swizzling

作者: dullgrass | 来源:发表于2017-07-07 14:55 被阅读840次

    黑魔法 Method_Swizzling

    • 原理: Method_Swizzling是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzle代码写到任何地方,但是只有在Method_Swizzling这段Method Swizzle代码执行完毕之后互换才起作用。
    • Method_Swizzling交换时机:尽可能在+load方法中实现
      • +load的执行时机:+load 方法会在加载类的时候就被调用,也就是 ios 应用启动的时候,就会加载所有的类,main函数之前,就会调用每个类的 +load 方法
      • 子类的+load方法会在它的所有父类(不包括父类分类中的+load)的+load方法执行之后执行
      • 分类的+load方法会在所有的主类的+load方法执行之后执行
      • 不同的类之间的+load方法的调用顺序是不确定的
      • + initialize:方法类似一个懒加载,initialize是在类或者其子类的第一个方法被调用前调用,且默认只加载一次;+ initialize 的调用发生在 +init 方法之前。
      • +load 和 + initialize 差异比较
    +(void)load +(void)initialize
    执行时机 在main函数之前执行 在类的方法被第一次调用时执行
    若自身未定义,是否沿用父类的方法?
    类别中的定义 全都执行,但后于类中的方法 覆盖类中的方法,只执行一次
    • 原子性:使用dispatch_once来执行方法交换,这样可以保证只运行一次
    • Method_Swizzling的两种实现
      • 直接交换: (在子类中交换方法,原始方法子类未实现,父类实现时,会将父类中的方法也交换)
    BOOL simple_Swizzle(Class aClass,SEL originalSel, SEL swizzleSel)
    {
             Method originalMethod = class_getInstanceMethod(aClass, originalSel);
             Method swizzleMethod = class_getInstanceMethod(aClass,swizzleSel);
             method_exchangeImplementations(originalMethod, swizzleMethod);
             return YES;   
    }
    
    • 示例: A类有方法work,B类继承自A类,有方法b_work,此时在B类分类的+load方法中交换work和b_work,并在b_work中调自身(回调回work),此时B对象b,调用work,实际调用的是b_work后再调用work(由于B继承自A,所以可以找得到work方法),A对象a,调用work,实际调用也是b_work,由于A类未实现b_work方法,出现崩溃
    • 先添加再交换:(保证只在子类中交换方法,不影响父类)
    • 这种方法会先尝试给自己添加work方法实现,如果B类没有实现work方法,class_addMethod会成功添加一个新的属于son自己的work方法,同时将本来要swizzle的方法的实现直接复制进work里,然后再将父类的IMP给swizzle
    BOOL best_Swizzle(Class aClass, SEL originalSel, SEL swizzleSel)
    {
             // 如果originalSel没有实现过,class_getInstanceMethod无法找到该方法,所以originalMethod为nil
             Method originalMethod = class_getInstanceMethod(aClass, originalSel);
             Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
             BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
             if (didAddMethod) {
                 //  当originalMethod为nil时,这里的class_replaceMethod将不做替换,所以swizzleSel方法里的实现还是自己原来的实现
                 class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
             } else {
                 method_exchangeImplementations(originalMethod, swizzleMethod);
             }
             return YES;
    }
    
    • swizzle一个子类到父类到根类都没有实现过的方法,会出现死循环,死循环是由于didAddMethod成功了,所以调用originalSel实际调用的是swizzleSel,class_replaceMethod交换失败了,所以在swizzleSel中调用originalSel,其实调用的还是自身,所以出现死循环
    • 解决方法是在originalMethod为nil时,替换后将swizzleSel复制一个不做任何事的空实现
    if (!originalMethod)  {
              class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
              method_setImplementation(swizzleMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));        
    }
    

    Method_Swizzling的具体用途AOP(面向切面编程)

    1. 页面统计(AOP)、NSMutableArray的insert等插入nil,hook: 給全局图片名称添加前缀,分类中为已有的属性或者方法添加钩子(增加一段代码)
    2. 用于记录或者存储,比方说记录ViewController进入次数、Btn的点击事件、ViewController的停留时间等等。 可以通过Runtime获取到具体ViewController、Btn信息,然后传给服务器
    3. 添加需要而系统没提供的方法,比方说修改Statusbar颜色。用于轻量化、模块化处理
    4. 用Method Swizzle 动态给指定的方法添加代码,以解决Cross-cutting concern的编程方式叫做Aspect Oriented Programming,将逻辑处理和事件记录的代码解耦
    5. AOP可以把琐碎的事务从主逻辑中分离出来,作为单独的模块,它是对面向对象编程模式的一种补充
    6. 比较好的AOP库,封装了runtime,Method Swizzling这些黑科技,该库只有两个API
          + (id<AspectToken>)aspect_hookSelector:(SEL)selector
                                      withOptions:(AspectOptions)options
                                       usingBlock:(id)block
                                            error:(NSError **)error;
          - (id<AspectToken>)aspect_hookSelector:(SEL)selector
                                      withOptions:(AspectOptions)options
                                       usingBlock:(id)block
                                            error:(NSError **)error;
    

    Objective-C类的继承、方法的重写和重载

    1. 继承:Objective-c中类的继承与C++类似,不同的是Objective-c不支持多重继承,一个类只能有一个父类,单继承使Objective-c的继承关系很简单,易于管理程序
    2. 重写:在Objective-c中,子类可继承父类中的方法,而不需要重新编写相同的方法,直接可以使用父类的方法。
      但有时我们不想使用使用父类方法,而是想作一定的修改,怎么办呢?只要将子类中书写一个与父类具有相同的方法名、返回类型和参数,就可以将将父类的方法覆盖重写
    • 如何只导入父类,执行子类的方法
    OverRide *overRide = (OverRide*)[[NSClassFromString(@"SubOverRide") alloc] init];
    [overRide superMethod];
    
    • 继承中方法调用的流程:首先到子类中去找,如果有该方法,就调用该方法,如果没有,就到父类中去找,父类没有就去父类的父类找,最后都没有找到,程序崩溃
    1. 重载:在Objective-c中,方法是不能重载的。也就是说我们不能在类中定义这样的两个方法:它们的名子相同,参数个数相同,参数类型不同,不同的返回值类型。否则Xcode会报错。

    引用

    相关文章

      网友评论

      • MemoryReload:BOOL best_Swizzle(Class aClass, SEL originalSel, SEL swizzleSel)
        {
        // 如果originalSel没有实现过,class_getInstanceMethod无法找到该方法,所以originalMethod为nil
        Method originalMethod = class_getInstanceMethod(aClass, originalSel);
        Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
        BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
        if (didAddMethod) {
        // 当originalMethod为nil时,这里的class_replaceMethod将不做替换,所以swizzleSel方法里的实现还是自己原来的实现
        class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
        method_exchangeImplementations(originalMethod, swizzleMethod);
        }
        return YES;
        }

        // 当originalMethod为nil时,这里的class_replaceMethod将不做替换,所以swizzleSel方法里的实现还是自己原来的实现

        这行注释简直是胡写!如果originalMethod为nil,添加方法的操作就会成功,判断条件didAddMethod正好为真,方法的exchange不会进行,恰好是repalce会执行,别扯求蛋了,行吗。扰乱视听。

      本文标题:iOS中的 Method_Swizzling

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