跳到主要内容

Method Swizzling

问题

Method Swizzling 的原理和注意事项?

答案

原理

交换两个方法的 IMP(实现指针):

交换前:
SEL viewDidLoad → IMP 原始实现
SEL my_viewDidLoad → IMP 自定义实现

交换后:
SEL viewDidLoad → IMP 自定义实现
SEL my_viewDidLoad → IMP 原始实现

实现

#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSEL = @selector(viewDidAppear:);
SEL swizzledSEL = @selector(track_viewDidAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSEL);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL);

// 先尝试添加(防止父类方法被替换)
BOOL didAdd = class_addMethod(class, originalSEL,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));

if (didAdd) {
class_replaceMethod(class, swizzledSEL,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

- (void)track_viewDidAppear:(BOOL)animated {
// 看起来递归,实际调用的是原始 viewDidAppear(因为 IMP 已交换)
[self track_viewDidAppear:animated];
// 自定义逻辑
NSLog(@"页面出现: %@", NSStringFromClass([self class]));
}

@end
注意事项
  1. 必须在 +load 中执行+load 在类加载时自动调用,保证初始化顺序
  2. 必须用 dispatch_once — 防止多次交换(偶数次交换等于没交换)
  3. class_addMethod — 防止替换了父类的方法
  4. "递归"调用原方法[self track_viewDidAppear:] 实际指向原实现
  5. 不要在 Swift 中滥用 — Swift 的静态分发不经过 objc_msgSend,需要 @objc dynamic

常见面试问题

Q1: +load 和 +initialize 的区别?

答案

  • +load:类加载到内存时调用,只调用一次,子类/分类各自调用
  • +initialize:类第一次收到消息时调用,可能被子类继承调用

Swizzling 必须在 +load,因为它在所有方法调用之前执行。

Q2: Swift 中能用 Method Swizzling 吗?

答案:可以,但方法必须标记 @objc dynamic(强制走消息机制)。纯 Swift 方法使用 VTable 分发,无法 Swizzle。

相关链接