You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

运行时交换特定类的未知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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:25:54