iOS 方法替换Swizzle Method

821 阅读2分钟

项目代码编写中,常会遇见第三方框架或者原生方法无法满足需求时或是一个方法在工程中大量被调用时,我们想要批量替换或修改,为了避免更改原有功能,在保持原有方法功能基础上,添加额外的功能,此时就需要用到Swizzle Method

方法替换Swizzle Method,目的是为了替换方法的实现。 平时实现某一方法时用到@selector,而能够实现方法是在@selector(方法选择器)中取出一方法编号(指向方法的指针),用SEL类型表示,它所指向的是一IMP(方法实现的指针),而方法替换的就是这个IMP,从而实现方法的替换

实例方法替换

#import <objc/runtime.h>

+ (void)swizzleInstanceMethodWithOriginalSEL:(SEL)originalSel SwizzleNewSEL:(SEL)newSel {
    
    Method originalMethod = class_getInstanceMethod(self, originalSel);
    Method newMethod = class_getInstanceMethod(self, newSel);
    if (!originalMethod || !newMethod) {
        return;
    }
    //加一层保护措施,如果添加成功,则表示该方法不存在于本类,而是存在于父类中,不能交换父类的方法,否则父类的对象调用该方法会crash;添加失败则表示本类存在该方法
    BOOL addMethod = class_addMethod(self, originalSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
    if (addMethod) {
        //再将原有的实现替换到swizzledMethod方法上,从而实现方法的交换,并且未影响到父类方法的实现
        class_replaceMethod(self, newSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, newMethod);
    }
    
}

类方法替换

#import <objc/runtime.h>

+ (void)swizzleClassMethodWithOriginalSEL:(SEL)originalSel SwizzleNewSEL:(SEL)newSel {
    Class class = object_getClass(self);
    Method originalMethod = class_getInstanceMethod(class, originalSel);
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!originalMethod || !newMethod) {
        return;
    }
    
    BOOL addMethod = class_addMethod(class, originalSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
    if (addMethod) {
        class_replaceMethod(class, newSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }else{
        method_exchangeImplementations(originalMethod, newMethod);
    }
    
}

替换方法的使用

+ (void)load{
    //方法替换处load不需要调用super,否则会导致父类被重复Swizzling
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{//保证方法替换只被执行一次

        [self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSArrayI") OriginalSEL:@selector(objectAtIndex:) SwizzleNewSEL:@selector(safe_objectAtIndex:)];
    });
}

  • 为了确保Swizzle Method方法替换能一定被执行调用,因此需要在load中执行(在装载类文件时就会被调用(程序启动前))

  • 为了避免子类或子类的子类调用了[super load]而导致Swizzling被执行了多次(相当于SEL和IMP被交换了多次。这就会导致第一次执行成功交换了、第二次执行又换回去了、第三次执行.....这样换来换去的结果),需要在load中使用GCD方法dispatch_once 确保方法只被执行一次并且 不需要 调用[super load]方法

利用方法替换统一处理数组越界、字符串下标越界及字典赋值为空时造成的崩溃问题_Demo