运行时交换特定类的未知Selector技术实现咨询
运行时动态Swizzle任意类/实例方法的解决方案
没问题,这个场景用Objective-C Runtime的动态特性完全能搞定。核心就是在运行时拿到目标方法的实现,替换成我们的自定义逻辑,执行完额外操作后再调用原方法。因为编译期不知道Selector的参数和签名,所以得靠Runtime的动态调用能力来适配任意方法形式。
核心思路拆解
- 区分类方法和实例方法的存储位置:类方法存在类的**元类(meta-class)**里,所以处理类方法时,得先用
object_getClass(cls)拿到元类;实例方法直接用原类就行。 - 动态生成通用的自定义实现:用
imp_implementationWithBlock把一个能处理可变参数的block转成IMP,这样不管原方法有多少参数、是什么返回类型,都能兼容。 - 安全替换方法实现:优先用
class_replaceMethod替换,如果失败(说明方法已存在),再用method_exchangeImplementations交换实现,同时要处理方法不存在的边界情况。
完整代码实现示例
下面是一个通用的Swizzle工具函数,支持类方法和实例方法,适配任意Selector:
#import <objc/runtime.h> #import <Foundation/Foundation.h> void swizzleTargetMethod(Class cls, SEL originalSelector, BOOL isClassMethod) { // 确定目标类:类方法用元类,实例方法用原类 Class targetClass = isClassMethod ? object_getClass(cls) : cls; // 获取原方法,处理方法不存在的情况 Method originalMethod = class_getInstanceMethod(targetClass, originalSelector); if (!originalMethod) { NSLog(@"Warning: Method %@ not found in %@", NSStringFromSelector(originalSelector), NSStringFromClass(targetClass)); return; } // 保存原方法的实现和类型编码 IMP originalImp = method_getImplementation(originalMethod); const char *methodTypeEncoding = method_getTypeEncoding(originalMethod); // 定义自定义逻辑的block,适配任意参数和返回值 id (^swizzledBlock)(id, SEL, ...) = ^id(id selfOrClass, SEL _cmd, ...) { // --- 这里插入你的前置额外逻辑 --- NSLog(@"About to execute method: %@ on %@", NSStringFromSelector(_cmd), NSStringFromClass([selfOrClass class])); // 调用原方法:用va_list处理可变参数,兼容任意参数数量 va_list args; va_start(args, _cmd); id result = nil; // 根据方法返回类型处理调用逻辑 if (strcmp(methodTypeEncoding, "v@:") == 0) { // 无返回值的方法 ((void (*)(id, SEL, ...))originalImp)(selfOrClass, _cmd, args); } else { // 有返回值的方法 result = ((id (*)(id, SEL, ...))originalImp)(selfOrClass, _cmd, args); } va_end(args); // --- 这里插入你的后置额外逻辑 --- NSLog(@"Finished executing method: %@", NSStringFromSelector(_cmd)); return result; }; // 将block转换为Runtime可识别的IMP IMP swizzledImp = imp_implementationWithBlock(swizzledBlock); // 尝试替换原方法,替换失败则交换实现(防止重复Swizzle) if (!class_replaceMethod(targetClass, originalSelector, swizzledImp, methodTypeEncoding)) { Method swizzledMethod = class_getInstanceMethod(targetClass, originalSelector); method_exchangeImplementations(originalMethod, swizzledMethod); } }
使用示例
- Swizzle类方法(比如
UIApplication的sharedApplication):swizzleTargetMethod([UIApplication class], @selector(sharedApplication), YES); - Swizzle实例方法(比如
UIViewController的viewDidLoad):swizzleTargetMethod([UIViewController class], @selector(viewDidLoad), NO);
关键注意事项
- 线程安全:建议在
+load或者+initialize方法中执行Swizzle,或者确保在单线程环境下执行,避免多线程竞争导致的异常。 - 精准适配复杂类型:如果需要适配结构体、基本数据类型等特殊返回值,推荐用
NSInvocation来处理,比直接用va_list更安全:// 替换原方法调用部分的NSInvocation实现 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[selfOrClass methodSignatureForSelector:_cmd]]; invocation.target = selfOrClass; invocation.selector = _cmd; // 动态设置所有参数 va_list args; va_start(args, _cmd); for (NSInteger i = 2; i < invocation.methodSignature.numberOfArguments; i++) { void *argValue = va_arg(args, void *); [invocation setArgument:argValue atIndex:i]; } va_end(args); [invocation invoke]; // 获取返回值 if (strcmp(methodTypeEncoding, "v@:") != 0) { [invocation getReturnValue:&result]; } - 避免重复Swizzle:可以通过
objc_setAssociatedObject给类添加关联标记,判断方法是否已经被Swizzle过,防止重复执行替换逻辑。
内容的提问来源于stack exchange,提问作者iOSAddicted
相关产品推荐
相关产品推荐

