让我详细总结一下 Method Swizzling 的使用和注意事项:
1. 基本实现
// 基本的 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 执行时机
// 1. 在 +load 方法中执行
+ (void)load {// 在这里执行 swizzling// 因为 +load 在类加载时调用,比较安全
}// 2. 避免在 +initialize 中执行
+ (void)initialize {// 不要在这里执行 swizzling// 因为 +initialize 可能被调用多次
}
2.2 线程安全
// 使用 dispatch_once 确保只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{// swizzling 代码
});
2.3 方法命名
// 使用清晰的前缀避免命名冲突
- (void)xyz_swizzledMethod {// 新的实现
}// 避免使用
- (void)swizzledMethod { // 可能造成命名冲突
}
2.4 子类处理
// 检查当前类是否真正实现了该方法
+ (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 方法签名
// 确保方法签名匹配
- (void)swizzled_method:(id)arg1 withObject:(id)arg2 {// 参数类型和数量必须与原方法完全匹配
}
3. 常见陷阱
3.1 递归调用
// 错误示例
- (void)swizzled_method {[self swizzled_method]; // 死循环!
}// 正确示例
- (void)swizzled_method {// 在 swizzling 后,这里会调用原始实现[self swizzled_method]; // 添加新功能
}
3.2 父类方法
// 避免重复 swizzling
static BOOL isSwizzled = NO;+ (void)load {if (!isSwizzled) {// 执行 swizzlingisSwizzled = YES;}
}
4. 最佳实践
4.1 封装 Swizzling
@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 文档化
// 清晰的文档说明
/*** 替换 viewDidAppear: 方法用于追踪页面显示* 警告:此方法会影响所有 UIViewController 实例* 在 +load 方法中调用,确保在应用启动时完成替换*/
+ (void)swizzleViewDidAppear {// swizzling 实现
}
总结:
- 在 +load 中执行
- 使用 dispatch_once
- 检查方法存在性
- 正确处理方法签名
- 避免递归调用
- 注意子类影响
- 清晰的命名约定
- 完善的文档说明
Method Swizzling 是一个强大但危险的特性,需要谨慎使用。