Method Swizzling 问题

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

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 问题)