我们可以用 Method Swizzling 来交换两个方法的实现,以便达到 Hook 的效果;

例如:交换 ViewController 生命周期方法来实现页面埋点,或者在不影响原有的功能增加一些特殊的功能。
例如:替换系统一些方法,比如数组 add 方法,在add 方法内添加判断,防止添加nil 或者越界等crash

交换方法主要是利用到 Runtime 中的 class_addMethod、class_replaceMethodmethod_exchangeImplementations 方法来实现的。

以下是 Method Swizzling 代码示例:

#pragma mark - 交换方法

+ (void)pxy_swizzleMethodWithOriginalSelector:(SEL)originalSeletor swizzledSelector:(SEL)swizzledSeletor {

         Class class = [self class];
         // 原有方法
         Method originMethod = class_getInstanceMethod(class, originalSeletor);
         // 替换原有方法的新方法
         Method swizzledMethod = class_getInstanceMethod(class, swizzledSeletor);

         //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
         BOOL didAddMethod = class_addMethod(class, originalSeletor,method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
         if (didAddMethod) {//添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
             class_replaceMethod(class, swizzledSeletor,
             method_getImplementation(originMethod),
             method_getTypeEncoding(originMethod));
        } else { //添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
             method_exchangeImplementations(originMethod, swizzledMethod);
        }
 }

实例:用runtime处理数组越界

我们为NSObject写一个分类(SwizzleMethod),方便我们调用交换系统和自定义的方法:

@interface NSObject (SwizzleMethod)
/**
 *  对系统方法进行替换(交换实例方法)
 *
 *  @param systemSelector 被替换的方法
 *  @param swizzledSelector 实际使用的方法
 *  @param error            替换过程中出现的错误消息
 *
 *  @return 是否替换成功
 */
+ (BOOL)SystemSelector:(SEL)systemSelector swizzledSelector:(SEL)swizzledSelector error:(NSError *)error;
@end

#import "NSObject+SwizzleMethod.h"
#import <objc/runtime.h>

@implementation NSObject (SwizzleMethod)

/**
 *  对系统方法进行替换
 *
 *  @param systemSelector 被替换的方法
 *  @param swizzledSelector 实际使用的方法
 *  @param error            替换过程中出现的错误消息
 *
 *  @return 是否替换成功
 */
+ (BOOL)SystemSelector:(SEL)systemSelector swizzledSelector:(SEL)swizzledSelector error:(NSError *)error{
    Method systemMethod = class_getInstanceMethod(self, systemSelector);
    if (!systemMethod) {
        return [[self class] unrecognizedSelector:systemSelector error:error];
    }

    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
    if (!swizzledMethod) {
        return [[self class] unrecognizedSelector:swizzledSelector error:error];
    }
    if (class_addMethod([self class], systemSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        class_replaceMethod([self class], swizzledSelector, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
    }else{
        method_exchangeImplementations(systemMethod, swizzledMethod);
    }
    return YES;
}

+ (BOOL)unrecognizedSelector:(SEL)selector error:(NSError *)error{
    NSString *errorString = [NSString stringWithFormat:@"%@类没有找到%@", NSStringFromClass([self class]), NSStringFromSelector(selector)];
    error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1userInfo:@{NSLocalizedDescriptionKey:errorString}];
    return NO;
}
@end

然后,给 NSMutableArray 添加分类(ErrorHandle),在.m文件中只需要写下如下代码:

#import "NSMutableArray+ErrorHandle.h"
#import "NSObject+SwizzleMethod.h"
#import <objc/runtime.h>
@implementation NSMutableArray (ErrorHandle)
+(void)load{
    [super load];
    //无论怎样 都要保证方法只交换一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //交换NSMutableArray中的方法
        [objc_getClass("__NSArrayM") SystemSelector:@selector(objectAtIndex:) swizzledSelector:@selector(jz_objectAtIndex:) error:nil];
        //交换NSMutableArray中的方法
        [objc_getClass("__NSArrayM") SystemSelector:@selector(objectAtIndexedSubscript:) swizzledSelector:@selector(jz_objectAtIndexedSubscript:) error:nil];
    });
}

- (id)jz_objectAtIndex:(NSUInteger)index{
    if (index < self.count) {
        return [self jz_objectAtIndex:index];
    }else{
        NSLog(@" 你的NSMutableArray数组已经越界 帮你处理好了%ld   %ld   %@", index, self.count, [self class]);
        return nil;
    }
}
- (id)jz_objectAtIndexedSubscript:(NSUInteger)index{
    if (index < self.count) {
        return [self jz_objectAtIndexedSubscript:index];
    }else{
        NSLog(@" 你的NSMutableArray数组已经越界 帮你处理好了%ld   %ld   %@", index, self.count, [self class]);
        return nil;
    }
}
@end