美文网首页
Method Swizzling 问题

Method Swizzling 问题

作者: 我是C | 来源:发表于2017-03-27 15:57 被阅读64次

    网上关于使用这个方法的教程很多,但是跟着教程走一遍发现一些问题,下面我来说说我对这个方法的理解.

    void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
    {
        // the method might not exist in the class, but in its superclass
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // class_addMethod will fail if original method already exists
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        
        // the method doesn’t exist and we just added one
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    

    上面是核心代码,目的是交换两个方法实现.

    SEL originalSelector   理解相当于源方法的Name
    SEL swizzledSelector   可以理解为调换的方法Name
    
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    要先尝试添加originalSelector是为了做一层保护,因为如果这个类没有实现originalSelector ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法.这时候违背了我们交换方法的意愿.
    

    现在具体的代码讲解完了,实践一下。举一个老生常谈的Logging方法,埋点问题。主要是探索用户习惯,对 App 的用户行为进行追踪和分析。

    建一个UIViewController的类别UIViewController+Logging

    + (void)load{
        swizzleMethod([self class], @selector(viewWillAppear:), @selector(new_viewWillAppear:));
    }
    
    void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector)
    {
        // the method might not exist in the class, but in its superclass
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // class_addMethod will fail if original method already exists
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        
        // the method doesn’t exist and we just added one
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    
    - (void)new_viewWillAppear:(BOOL)animated{
        
    }
    
    

    +load: 是个特例,当一个类被读到内存的时候, runtime 会给这个类及它的每一个类别都发送一个 +load: 消息。

    注意: new_viewWillAppear里并没有写方法,正常应该是:

    - (void)new_viewWillAppear:(BOOL)animated{
        [self new_viewWillAppear:animated];
    }
    这时候调用自身方法实际是调用的原viewWillAppear的IMP,因为上面已经将两个方法的IMP交换了。
    

    跑一下代码,发现会先走的是子类的viewWillAppear方法, 然后才会走你类别的new_viewWillAppear方法.你发现你晕了,我已经交换了IMP啊,为啥还会走子类的viewWillAppear,问题出现在:

    + (void)load{
        swizzleMethod([self class], @selector(viewWillAppear:), @selector(new_viewWillAppear:));
    }
    中的[self class],打印它,它是:UIViewController。
    

    原来我们hook了父类的viewWillAppear,交换的实际是UIViewController 中的viewWillAppear方法,将子类viewWillAppear中的[super viewWillAppear:animated]删除,你会发现,此时类别中的- (void)new_viewWillAppear:(BOOL)animated不会走了。

    现在你会问:既然父类的方法被类别中的替换了,那么子类也应该也被替换.
    答案:
    方法并不会重写,只会在method list里面插入的位置靠前,category写一个跟类里面名字 方法签名一模一样的方法 还可以有办法可以调用到类原来的这个方法。
    注意:在交换方法的时候一定要注意,hook 的是哪个类,这个类是不是你想要交换的方法的那个类。

    欢迎各位同学指正错误,我会努力及时改正!

    相关文章

      网友评论

          本文标题:Method Swizzling 问题

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