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
注意事项
- 必须在
+load中执行 —+load在类加载时自动调用,保证初始化顺序 - 必须用
dispatch_once— 防止多次交换(偶数次交换等于没交换) - 先
class_addMethod— 防止替换了父类的方法 - "递归"调用原方法 —
[self track_viewDidAppear:]实际指向原实现 - 不要在 Swift 中滥用 — Swift 的静态分发不经过
objc_msgSend,需要@objc dynamic
常见面试问题
Q1: +load 和 +initialize 的区别?
答案:
+load:类加载到内存时调用,只调用一次,子类/分类各自调用+initialize:类第一次收到消息时调用,可能被子类继承调用
Swizzling 必须在 +load,因为它在所有方法调用之前执行。
Q2: Swift 中能用 Method Swizzling 吗?
答案:可以,但方法必须标记 @objc dynamic(强制走消息机制)。纯 Swift 方法使用 VTable 分发,无法 Swizzle。