Objective-C Method Swizzling

本文详细介绍了Objective-C中的Method Swizzling技术,探讨了如何通过+load方法实现在运行时动态交换方法实现的逻辑。文章对比了两种常见实现方案,并分析了不同情况下的Hook结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

切入正题

Objective-C中的Hook又被称作 Method Swizzling ,这是动态语言都具有的特性。在Objective-C中经常会把Hook的逻辑写在 +load 方法中,有时候需要Hook子类和父类的同一个方法,但是它们的 +load 方法调用顺序不同。一个常见的顺序可能是:父类 ->子类 -> 子类类别 ->父类类别。所以Hook的顺序并不能保证,就不能保证Hook后方法调用的顺序是对的。而且使用不同方法Method Swizzing也会带来不同的结果。本文将会对这些情况下的Hook结果进行分析和总结。

Method Swizzling常用实现方案

方案A:如果类中没有实现Original selector对应的方法,那就先添加Method,并将其IMP映射为Swizzle的实现。然后替换Swizzle selector的IMP为Original的实现;否则交换二者IMP。

+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
        Class aClass = [self class];

        SEL originalSelector = @selector(method_original:);
        SEL swizzledSelector = @selector(method_swizzle:);

        Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

        BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

        if(didAddMethod) {
            class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));

        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }

    });
}

有时为了避免方法命名冲突和参数_cmd被篡改,也会使用下面这种静态方法版本的Method Swizzle。CaptainHook中的宏定义也是采用这种方式,比较推荐:

typedef IMP *IMPPointer;
static void MethodSwizzle(id self,SEL _cmd, id arg1);
static void (*MethodOriginal)(id self,SEL _cmd, id arg1);

static void MethodSwizzle(id self, SEL _cmd, id arg1) {
    MethodOriginal(self,_cmd,arg1);

}

BOOL class_swizzleMethodAndStore(Class class, SEL original,IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if(method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if(!imp) {
            imp = method_getImplementation(method);
        }
    }
    if(imp && store) {
        *store = imp;
    }
    return (imp != NULL);
}

+(BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}

+(void)load {
    [self swizzle:@selector(originalMethod:) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
}

方案B
实质对方案A的缩减版:

+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class aClass = [self class];

        SEL originalSelector = @selector(method_original:);
        SEL swizzledSelector = @selector(method_swizzle:);

        Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

直接交换IMP是很危险的。因为如果这个类中没有实现这个方法,class_getInstanceMethod()返回的是某个类的Method对象,这样method_exchangeImplementations()就把父类的原始方法实现(IMP)跟这个类的Swizzle实现交换。这样其他父类与其他子类的方法调用就会出现问题,最严重的就是crash。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值