让我详细总结一下 Method Swizzling 的使用和注意事项:
1. 基本实现
objectivec
// 基本的 Method Swizzling 实现
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 1. 获取原始方法和替换方法
Method originalMethod = class_getInstanceMethod(self, @selector(viewDidAppear:));
Method swizzledMethod = class_getInstanceMethod(self, @selector(track_viewDidAppear:));
// 2. 尝试添加方法实现
BOOL didAddMethod = class_addMethod(self,
@selector(viewDidAppear:),
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
// 3. 如果添加成功,直接替换实现
if (didAddMethod) {
class_replaceMethod(self,
@selector(track_viewDidAppear:),
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
// 4. 添加失败,说明方法已存在,直接交换实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
// 新方法实现
- (void)track_viewDidAppear:(BOOL)animated {
// 1. 调用原始实现
[self track_viewDidAppear:animated]; // 注意:这里实际会调用原始的 viewDidAppear:
// 2. 添加新的功能
[self trackViewPageAppear];
}
@end
2. 注意事项
2.1 执行时机
objectivec
// 1. 在 +load 方法中执行
+ (void)load {
// 在这里执行 swizzling
// 因为 +load 在类加载时调用,比较安全
}
// 2. 避免在 +initialize 中执行
+ (void)initialize {
// 不要在这里执行 swizzling
// 因为 +initialize 可能被调用多次
}
2.2 线程安全
objectivec
// 使用 dispatch_once 确保只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// swizzling 代码
});
2.3 方法命名
objectivec
// 使用清晰的前缀避免命名冲突
- (void)xyz_swizzledMethod {
// 新的实现
}
// 避免使用
- (void)swizzledMethod { // 可能造成命名冲突
}
2.4 子类处理
objectivec
// 检查当前类是否真正实现了该方法
+ (void)swizzleMethod:(SEL)originalSelector {
Class class = [self class];
// 确保方法存在于当前类,而不是继承自父类
Method originalMethod = class_getInstanceMethod(class, originalSelector);
if (method_getImplementation(originalMethod) == method_getImplementation(class_getInstanceMethod([NSObject class], originalSelector))) {
return; // 方法来自基类,不进行 swizzling
}
// 进行 swizzling
}
2.5 方法签名
objectivec
// 确保方法签名匹配
- (void)swizzled_method:(id)arg1 withObject:(id)arg2 {
// 参数类型和数量必须与原方法完全匹配
}
3. 常见陷阱
3.1 递归调用
objectivec
// 错误示例
- (void)swizzled_method {
[self swizzled_method]; // 死循环!
}
// 正确示例
- (void)swizzled_method {
// 在 swizzling 后,这里会调用原始实现
[self swizzled_method];
// 添加新功能
}
3.2 父类方法
objectivec
// 避免重复 swizzling
static BOOL isSwizzled = NO;
+ (void)load {
if (!isSwizzled) {
// 执行 swizzling
isSwizzled = YES;
}
}
4. 最佳实践
4.1 封装 Swizzling
objectivec
@implementation NSObject (Swizzling)
+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)altSel {
Method origMethod = class_getInstanceMethod(self, origSel);
Method altMethod = class_getInstanceMethod(self, altSel);
if (!origMethod || !altMethod) {
return NO;
}
class_addMethod(self,
origSel,
class_getMethodImplementation(self, origSel),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel,
class_getMethodImplementation(self, altSel),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origSel),
class_getInstanceMethod(self, altSel));
return YES;
}
@end
4.2 文档化
objectivec
// 清晰的文档说明
/**
* 替换 viewDidAppear: 方法用于追踪页面显示
* 警告:此方法会影响所有 UIViewController 实例
* 在 +load 方法中调用,确保在应用启动时完成替换
*/
+ (void)swizzleViewDidAppear {
// swizzling 实现
}
总结:
- 在 +load 中执行
- 使用 dispatch_once
- 检查方法存在性
- 正确处理方法签名
- 避免递归调用
- 注意子类影响
- 清晰的命名约定
- 完善的文档说明
Method Swizzling 是一个强大但危险的特性,需要谨慎使用。