谈谈Method-Swizzing的那些坑点

demo地址
1.如果在子类的拓展中通过+load交换的父类方法,也就是说子类没实现,父类实现了,当使用父类调用方法时就会崩溃,比如

父类:

@interface FYPerson : NSObject
-(void)eatFood;
@end

子类

@interface FYStudent : FYPerson
- (void)study;
@end

方法交换


image.png

调用


image.png

这种崩溃产生的原因主要是因为,父类在调用eatfood的时候找到了交换的IMP,但是在父类中并没有newInstaceMethod的imp,而是在子类中,也就会报找不到imp的错误


image.png

如果父类中也有newInstanceMethod方法的,那么就可以找到imp而避免崩溃

+(void)better_methodSwizzingWithClass:(Class)cls oriSEL: (SEL)oriSEL SwizzledSEL: (SEL)swizzledSEL
{
   
    if (!cls) NSLog(@"Class is nil");
    //原始方法
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    //新方法
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    //针对父类中没有实现交换方法,那么我们尝试放父类添加一个swizzledSEL的实现
    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);
    }
}

如果原始方法本身就没有实现,那么在交换的时候也就自然交换了个寂寞,所以在调用的时候就会产生递归,导致堆栈溢出,所以在如果原始方法没有实现,也要解决:

+(void)safe_methodSwizzingWithClass:(Class)cls oriSEL: (SEL)oriSEL SwizzledSEL: (SEL)swizzledSEL {
    if (!cls) NSLog(@"Class is nil");
    //原始方法
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    //新方法
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
   
    //如果原始方法没有实现,来个默认实现,避免交换了个寂寞
    if (!oriMethod) {
        //oriMethod为nil时,oriSEL添加了 swiMethod 的imp,替换后将swizzledSEL添加一个不做任何事的空实现imp
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
            NSLog(@"%@方法没有实现!!!,添加了默认实现IMP",self);
        }));
    } else {
        //针对父类中没有实现交换方法,那么我们尝试放父类添加一个swizzledSEL的实现
        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);
        }
    }
}

如果原始方法没有实现,那么将交换方法的imp给原始方法,替换后,将替换方法的imp设置一个默认实现,这样也就完成了交换。

问题遗留,这种方式虽然可以解决原始方法没有实现的问题,但是当父类没有实现,并且使用父类这样调用的时候:

FYPerson *person = [FYPerson alloc];
[person eatFood];

还是会出现崩溃,暂未解决,虽然一般不会这么调用,如果大家有什么解决方案欢迎留言讨论

你可能感兴趣的:(谈谈Method-Swizzing的那些坑点)