iOS方法交换(Swizzling)最佳实践与坑点、线程安全详解

20 阅读16分钟

在iOS开发中,Objective-C的Runtime机制赋予了我们“动态修改方法实现”的能力,而方法交换(Method Swizzling,简称“方法混写”) 就是其中最具实用性的技巧之一。它可以在不修改原有代码的前提下,替换类的方法实现,实现AOP(面向切面编程)、Hook系统方法、统一埋点、异常捕获等场景,是iOS进阶开发中不可或缺的技能。

但方法交换看似简单,实则暗藏诸多陷阱:线程安全问题、重复交换导致崩溃、父类子类冲突、系统方法Hook风险等,稍有不慎就会引发线上崩溃。本文将从“原理→最佳实践→坑点解析→线程安全→多场景示例”五个维度,彻底讲透方法交换,帮你安全、规范地运用这一Runtime黑魔法。

前置说明:本文基于Objective-C Runtime源码(objc4-818.2)展开,兼顾OC与Swift(Swift需通过OC桥接或@objc修饰符),所有示例均可直接复制运行,适合有一定Runtime基础的iOS开发者。

一、先搞懂:方法交换的底层原理

要用好方法交换,必须先理解其底层逻辑——核心是“修改方法的实现指针”,而非修改方法本身。

1. 核心概念:Method、SEL、IMP

在OC中,每个类(Class)都维护着一个方法列表(methodList) ,列表中的每个元素都是一个Method结构体(源码定义简化如下):

// 简化的Method结构体(objc4源码)
struct method_t {
    SEL name;          // 方法选择器(如@selector(viewDidLoad))
    const char *types; // 方法签名(参数、返回值类型)
    IMP imp;           // 方法实现的函数指针(指向具体的代码逻辑)
};

关键关系:

  • SEL:方法的“名字”,用于标识方法(如@selector(viewDidLoad)),不同类可以有相同的SEL,但指向不同的IMP。
  • IMP:方法的“实现”,是一个函数指针,指向方法的具体代码逻辑,调用方法本质就是调用IMP指向的函数。
  • 方法交换的核心:交换两个Method的IMP指针,让SEL指向另一个方法的实现,从而实现“调用A方法,执行B方法逻辑”。

2. 核心API(Runtime提供)

方法交换依赖3个核心Runtime API,必须熟练掌握,其作用和用法如下:

API作用用法示例
class_getInstanceMethod获取类的实例方法(非类方法)Method method = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
class_getClassMethod获取类的类方法(如+[NSString stringWithFormat:])Method method = class_getClassMethod([NSString class], @selector(stringWithFormat:));
method_exchangeImplementations交换两个Method的IMP指针(核心API)method_exchangeImplementations(method1, method2);

3. 最简示例:理解方法交换的基本流程

以“Hook UIViewController的viewDidLoad方法,添加日志打印”为例,看最基础的方法交换实现:

#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@interface UIViewController (SwizzlingDemo)
@end

@implementation UIViewController (SwizzlingDemo)

// 方法交换必须在“类加载时”执行(只执行一次)
+ (void)load {
    // 1. 获取两个需要交换的方法
    Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
    Method swizzledMethod = class_getInstanceMethod([self class], @selector(swizzled_viewDidLoad));
    
    // 2. 交换方法实现
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

// 替换后的方法(自定义逻辑)
- (void)swizzled_viewDidLoad {
    // 3. 调用原方法实现(注意:此时swizzled_viewDidLoad已和viewDidLoad交换,调用自身就是调用原方法)
    [self swizzled_viewDidLoad];
    
    // 4. 自定义逻辑(如打印日志)
    NSLog(@"[%@] viewDidLoad 被调用", [self class]);
}

@end

关键说明:

  • 方法交换必须在+load方法中执行:+load是类加载时自动调用的方法,且只执行一次,避免重复交换(重复交换会还原方法实现,导致逻辑错乱)。
  • 调用原方法的技巧:交换后,@selector(viewDidLoad)指向swizzled_viewDidLoad的IMP,@selector(swizzled_viewDidLoad)指向原viewDidLoad的IMP,因此调用[self swizzled_viewDidLoad]就是执行原方法逻辑。

二、方法交换最佳实践(必遵循,避坑核心)

方法交换的坑,大多源于“不规范的实现”。以下5条最佳实践,是一线开发中总结的“避坑准则”,必须严格遵循。

最佳实践1:在+load方法中执行交换,且加线程安全锁

前文提到,+load方法会在类加载时自动调用,且只执行一次,但存在“多线程并发加载类”的场景(如APP启动时),此时不加锁可能导致方法交换不完整,引发崩溃。

正确示例(加锁保证线程安全):

+ (void)load {
    // 加线程安全锁,避免多线程并发交换
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(swizzled_viewDidLoad));
        
        // 关键:判断方法是否存在,避免nil指针崩溃
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

核心点:用dispatch_once(本质是自旋锁)保证“方法交换只执行一次”,同时避免多线程并发问题;添加方法存在性判断,防止获取不到Method导致的nil指针崩溃。

最佳实践2:自定义方法名加前缀,避免方法名冲突

若自定义的交换方法名(如swizzled_viewDidLoad)与系统方法、其他分类的方法重名,会导致方法覆盖,引发未知崩溃。因此,自定义方法必须加唯一前缀(如项目前缀+功能标识)。

错误示例(无前缀,易冲突):

// 错误:方法名无前缀,若其他分类也实现了customViewDidLoad,会覆盖
- (void)customViewDidLoad {
    [self customViewDidLoad]; // 调用原方法
    NSLog(@"viewDidLoad 被Hook");
}

正确示例(加项目前缀,如YT):

// 正确:加前缀YT,避免冲突
- (void)YT_swizzled_viewDidLoad {
    [self YT_swizzled_viewDidLoad]; // 调用原方法
    NSLog(@"[%@] viewDidLoad 被Hook", [self class]);
}

最佳实践3:Hook系统方法时,兼容子类重写场景

当Hook系统类(如UIViewController)的方法时,若子类重写了该方法(如自定义ViewController重写viewDidLoad),直接交换会导致子类的方法逻辑被覆盖。解决方案:给系统类添加“备用方法”,避免直接替换原方法

正确示例(兼容子类重写):

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [UIViewController class];
        SEL originalSEL = @selector(viewDidLoad);
        SEL swizzledSEL = @selector(YT_swizzled_viewDidLoad);
        
        // 1. 获取原方法和自定义方法
        Method originalMethod = class_getInstanceMethod(cls, originalSEL);
        Method swizzledMethod = class_getInstanceMethod(cls, swizzledSEL);
        
        // 2. 关键:给原类添加“备用方法”(若原方法被子类重写,不会影响子类)
        // 若添加失败(说明方法已存在),直接交换;若添加成功,再交换
        BOOL didAddMethod = class_addMethod(cls, originalSEL, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            // 添加成功:将自定义方法的实现替换为原方法
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            // 添加失败:直接交换两个方法的实现
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

// 自定义交换方法
- (void)YT_swizzled_viewDidLoad {
    // 调用原方法(此时originalSEL已指向自定义方法,swizzledSEL指向原方法)
    [self YT_swizzled_viewDidLoad];
    
    // 自定义逻辑
    NSLog(@"系统viewDidLoad Hook 成功,兼容子类重写");
}

关键逻辑:通过class_addMethod给系统类添加原方法的“备用实现”,若子类重写了该方法,子类的方法不会被影响,从而避免子类逻辑被覆盖。

最佳实践4:避免Hook“私有方法”和“不稳定的系统方法”

系统私有方法(如带下划线的方法,_xxx)的实现可能在iOS版本更新中被修改或删除,Hook这类方法会导致APP在新版本系统中崩溃;此外,部分系统方法(如UIView的layoutSubviews)的调用时机复杂,Hook后易引发布局错乱。

建议:

  • 优先Hook“公开方法”(如viewDidLoad、viewWillAppear:),这类方法的兼容性更稳定。
  • 若必须Hook私有方法,需做iOS版本判断,且做好异常捕获(try-catch)。

最佳实践5:及时“还原方法交换”(可选,针对临时Hook场景)

若方法交换是“临时需求”(如调试、临时埋点),需在不需要时还原方法实现,避免影响其他业务逻辑。还原的核心是“再次交换两个方法”(因为交换两次会回到原始状态)。

示例(还原方法交换):

// 还原方法交换(在合适的时机调用,如页面销毁时)
+ (void)restoreSwizzling {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod([UIViewController class], @selector(YT_swizzled_viewDidLoad));
        
        if (originalMethod && swizzledMethod) {
            // 再次交换,还原原始实现
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

三、高频坑点解析(附错误示例+解决方案)

方法交换的坑,90%都集中在“重复交换”“线程安全”“子类冲突”“方法不存在”这4个场景,以下逐一解析,结合错误示例和解决方案,帮你避开陷阱。

坑点1:重复交换导致方法实现还原,逻辑错乱

错误示例(未加dispatch_once,导致重复交换):

+ (void)load {
    // 错误:未加dispatch_once,若类被多次加载(如动态加载),会重复交换
    Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
    Method swizzledMethod = class_getInstanceMethod([self class], @selector(YT_swizzled_viewDidLoad));
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

问题分析:+load虽然默认只执行一次,但在动态加载类(如插件化、反射加载)的场景下,可能会被多次调用;重复交换会导致方法实现“还原”(交换两次=未交换),自定义逻辑无法执行。

解决方案:必须用dispatch_once包裹交换逻辑,保证“只执行一次”(如最佳实践1所示)。

坑点2:未判断方法存在,导致nil指针崩溃

错误示例(未判断Method是否为nil):

+ (void)load {
    dispatch_once(&onceToken, ^{
        // 错误:未判断方法是否存在,若原方法不存在(如拼写错误),originalMethod为nil
        Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad123)); // 拼写错误
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(YT_swizzled_viewDidLoad));
        method_exchangeImplementations(originalMethod, swizzledMethod); // 传入nil,崩溃
    });
}

问题分析:若原方法不存在(如SEL拼写错误、类错误),class_getInstanceMethod会返回nil,将nil传入method_exchangeImplementations会直接引发崩溃。

解决方案:添加Method存在性判断,只有两个Method都不为nil时,才执行交换(如最佳实践1所示)。

坑点3:Hook子类方法,影响父类逻辑

错误示例(直接Hook子类方法,未区分父类):

// 自定义子类:YTViewController(继承自UIViewController)
@interface YTViewController : UIViewController
@end

@implementation YTViewController (Swizzling)
+ (void)load {
    dispatch_once(&onceToken, ^{
        // 错误:直接Hook子类的viewDidLoad,会影响父类UIViewController的所有子类
        Method originalMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(YT_swizzled_viewDidLoad));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)YT_swizzled_viewDidLoad {
    [self YT_swizzled_viewDidLoad];
    NSLog(@"YTViewController viewDidLoad 被Hook");
}
@end

问题分析:Hook子类YTViewController的viewDidLoad后,若其他子类(如YTSecondViewController)也继承自UIViewController,且未重写viewDidLoad,会默认调用被Hook后的实现,导致逻辑污染。

解决方案:Hook子类方法时,明确指定子类Class,且避免在子类分类中Hook父类方法;若需Hook所有子类,建议在父类分类中实现(如最佳实践3所示)。

坑点4:方法签名不匹配,导致崩溃

错误示例(自定义方法与原方法签名不一致):

// 原方法:viewWillAppear:(BOOL)animated(有参数)
// 自定义方法:无参数,签名不匹配
- (void)YT_swizzled_viewWillAppear {
    [self YT_swizzled_viewWillAppear]; // 调用原方法时,参数不匹配,崩溃
    NSLog(@"viewWillAppear 被Hook");
}

+ (void)load {
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewWillAppear:));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(YT_swizzled_viewWillAppear));
        method_exchangeImplementations(originalMethod, swizzledMethod); // 签名不匹配,崩溃
    });
}

问题分析:方法交换要求“两个方法的签名(参数、返回值类型)必须完全一致”,否则调用时会出现参数错乱、栈溢出,直接崩溃。

解决方案:自定义交换方法的签名,必须与原方法完全一致(包括参数个数、类型、返回值):

// 正确:自定义方法与原方法签名一致(有BOOL参数)
- (void)YT_swizzled_viewWillAppear:(BOOL)animated {
    [self YT_swizzled_viewWillAppear:animated]; // 传入参数,匹配原方法
    NSLog(@"viewWillAppear: %@", animated ? @"YES" : @"NO");
}

四、线程安全:方法交换的核心保障

方法交换的线程安全,分为两个层面:方法交换过程的线程安全交换后方法执行的线程安全,两者缺一不可。

1. 方法交换过程的线程安全(必做)

方法交换是“修改类的方法列表”的操作,若多线程同时执行交换(如APP启动时,多个类同时加载),会导致方法列表错乱,引发崩溃。解决方案:用dispatch_once包裹交换逻辑。

核心原理:dispatch_once内部使用自旋锁,保证代码块“只执行一次”,且在多线程环境下不会并发执行,从而避免交换过程中的线程安全问题(如最佳实践1所示)。

补充:不建议用@synchronized,因为@synchronized是互斥锁,性能比自旋锁差,且在+load方法中使用可能引发死锁。

2. 交换后方法执行的线程安全(按需做)

方法交换后,自定义逻辑(如日志打印、埋点)若涉及“共享资源访问”(如全局变量、UI操作),需保证执行过程的线程安全,否则会出现数据错乱、UI卡顿等问题。

示例(交换后方法的线程安全处理):

- (void)YT_swizzled_viewDidLoad {
    [self YT_swizzled_viewDidLoad]; // 调用原方法
    
    // 场景1:访问共享资源(如全局变量),加锁保护
    static NSInteger count = 0;
    @synchronized (self) {
        count++;
        NSLog(@"viewDidLoad 调用次数:%ld", count);
    }
    
    // 场景2:UI操作,切换到主线程
    dispatch_async(dispatch_get_main_queue(), ^{
        // 避免在子线程操作UI,引发卡顿或崩溃
        self.view.backgroundColor = [UIColor lightGrayColor];
    });
}

关键原则:

  • 共享资源访问:用@synchronizedNSLock加锁,避免多线程并发修改。
  • UI操作:必须在主线程执行,用dispatch_async(dispatch_get_main_queue(), ^{...})切换线程。

3. 特殊场景:多线程下Hook系统方法的线程安全

若Hook的是“多线程调用的系统方法”(如NSURLSession的回调方法),需额外注意:原方法可能在子线程执行,自定义逻辑需适配子线程环境,避免线程安全问题。

示例(Hook NSURLSession的回调方法,保证线程安全):

@implementation NSURLSessionDataTask (Swizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(resume));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(YT_swizzled_resume));
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)YT_swizzled_resume {
    [self YT_swizzled_resume]; // 调用原方法(resume)
    
    // 注意:resume可能在子线程调用,自定义逻辑需保证线程安全
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 子线程执行耗时操作(如日志上报)
        NSLog(@"NSURLSessionDataTask 开始请求,taskId: %@", self.taskIdentifier);
    });
}
@end

五、多场景实战示例(可直接复制运行)

结合实际开发场景,提供4个高频示例,覆盖“系统方法Hook、异常捕获、埋点、自定义类方法交换”,帮你快速落地方法交换。

示例1:Hook UIViewController的viewDidAppear,统一添加埋点

#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@interface UIViewController (YT_BuriedPoint)
@end

@implementation UIViewController (YT_BuriedPoint)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [UIViewController class];
        SEL originalSEL = @selector(viewDidAppear:);
        SEL swizzledSEL = @selector(YT_swizzled_viewDidAppear:);
        
        Method originalMethod = class_getInstanceMethod(cls, originalSEL);
        Method swizzledMethod = class_getInstanceMethod(cls, swizzledSEL);
        
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)YT_swizzled_viewDidAppear:(BOOL)animated {
    [self YT_swizzled_viewDidAppear:animated]; // 调用原方法
    
    // 统一埋点:上报页面显示事件
    NSString *pageName = NSStringFromClass([self class]);
    NSLog(@"【埋点】页面显示:%@", pageName);
    // 实际开发中,这里可以调用埋点SDK的上报方法
    // [YTBuriedPointManager reportEvent:@"page_show" params:@{@"page_name": pageName}];
}

@end

示例2:Hook NSString的stringWithFormat:,捕获格式错误异常

场景:开发中常因stringWithFormat:的格式符与参数不匹配,导致崩溃,通过方法交换捕获异常,避免崩溃。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface NSString (YT_ExceptionCatch)
@end

@implementation NSString (YT_ExceptionCatch)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = [NSString class];
        // 注意:stringWithFormat:是类方法,用class_getClassMethod
        Method originalMethod = class_getClassMethod(cls, @selector(stringWithFormat:));
        Method swizzledMethod = class_getClassMethod(cls, @selector(YT_swizzled_stringWithFormat:));
        
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

// 类方法交换:注意加+号
+ (NSString *)YT_swizzled_stringWithFormat:(NSString *)format, ... {
    // 捕获异常,避免崩溃
    @try {
        // 调用原方法(注意:类方法用self调用)
        va_list args;
        va_start(args, format);
        NSString *result = [self YT_swizzled_stringWithFormat:format arguments:args];
        va_end(args);
        return result;
    } @catch (NSException *exception) {
        // 异常处理:打印日志,返回默认值
        NSLog(@"【异常】stringWithFormat 格式错误:%@", exception);
        return @""; // 返回默认值,避免崩溃
    } @finally {
        // 可选:清理资源
    }
}

@end

示例3:Hook UIButton的click方法,统一处理按钮点击防抖

#import <UIKit/UIKit.h>
#import <objc/runtime.h>

@interface UIButton (YT_AntiShake)
// 关联对象:用于存储按钮点击的时间戳(避免重复点击)
@property (nonatomic, assign) NSTimeInterval yt_lastClickTime;
@end

@implementation UIButton (YT_AntiShake)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(sendAction:to:forEvent:));
        Method swizzledMethod = class_getInstanceMethod([self class], @selector(YT_swizzled_sendAction:to:forEvent:));
        
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

// 交换sendAction:to:forEvent:(按钮点击的核心方法)
- (void)YT_swizzled_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    // 防抖逻辑:两次点击间隔小于0.5秒,不执行点击
    NSTimeInterval currentTime = CACurrentMediaTime();
    if (currentTime - self.yt_lastClickTime < 0.5) {
        NSLog(@"【防抖】按钮重复点击,忽略");
        return;
    }
    self.yt_lastClickTime = currentTime;
    
    // 调用原方法,执行正常点击逻辑
    [self YT_swizzled_sendAction:action to:target forEvent:event];
}

// 关联对象:yt_lastClickTime的setter/getter
- (void)setYt_lastClickTime:(NSTimeInterval)yt_lastClickTime {
    objc_setAssociatedObject(self, @selector(yt_lastClickTime), @(yt_lastClickTime), OBJC_ASSOCIATION_ASSIGN);
}

- (NSTimeInterval)yt_lastClickTime {
    return [objc_getAssociatedObject(self, @selector(yt_lastClickTime)) doubleValue];
}

@end

示例4:Swift中使用方法交换(OC桥接)

Swift中无法直接使用Runtime API,需通过OC桥接,或给Swift类添加@objc修饰符,实现方法交换。

步骤1:创建OC桥接文件(如YTMethodSwizzling.h),声明交换方法:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface NSObject (YT_Swizzling)
+ (void)yt_swizzleInstanceMethodWithOriginalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL;
@end

@implementation NSObject (YT_Swizzling)
+ (void)yt_swizzleInstanceMethodWithOriginalSEL:(SEL)originalSEL swizzledSEL:(SEL)swizzledSEL {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], originalSEL);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSEL);
        
        if (originalMethod && swizzledMethod) {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
@end

步骤2:Swift类中使用(给Swift类添加@objc修饰符):

import UIKit

// 给Swift类添加@objc修饰符,支持Runtime
@objc class YTSwiftViewController: UIViewController {
    @objc func originalMethod() {
        print("原始方法执行")
    }
    
    @objc func swizzledMethod() {
        // 调用原方法
        swizzledMethod()
        // 自定义逻辑
        print("Swift方法交换成功,添加自定义逻辑")
    }
}

// 在合适的时机(如APP启动时)执行交换
extension YTSwiftViewController {
    override class func load() {
        super.load()
        yt_swizzleInstanceMethod(withOriginalSEL: #selector(originalMethod), swizzledSEL: #selector(swizzledMethod))
    }
}

// 使用
let vc = YTSwiftViewController()
vc.originalMethod() // 输出:原始方法执行 → Swift方法交换成功,添加自定义逻辑

六、总结:方法交换的“使用准则”

方法交换是一把“双刃剑”:它能帮我们高效实现AOP、Hook等场景,减少代码侵入;但不规范的使用会引发崩溃、逻辑错乱等问题。记住以下4条准则,就能安全运用:

  1. 规范实现:在+load中用dispatch_once包裹交换逻辑,加方法存在性判断,自定义方法加唯一前缀。
  2. 避免滥用:优先用“组合、继承”实现需求,只有在“无法修改原有代码”(如系统类、第三方库)时,才使用方法交换。
  3. 关注线程安全:交换过程用dispatch_once,交换后方法执行时,根据场景加锁、切换线程。
  4. 做好兼容:Hook系统方法时,适配子类重写场景,避免Hook私有方法,做好版本兼容和异常捕获。