老青菜

iOS Method Swizzle 底层分析

2018-10-15

Method

Method:方法结构体,在iOSclass结构中,存储了方法列表objc_method_list,列表中包含了方法method_t指针、method_t数量。我们看下objc源码。

//objc-runtime-new.mm

struct objc_class {
    Class isa ;
    struct objc_method_list **methodLists;
}
struct objc_method_list {
    int method_count;
    struct objc_method method_list[1];
}  

typedef struct method_t *Method;
struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

不难发现,objc中,方法Method包含了SELIMP

SEL

SEL:类成员方法的指针,但不同于C语言中的函数指针,函数指针直接保存了方法的地址,但SEL只是方法编号。runtime并没有关于SEL结构体的定义。

typedef struct objc_selector *SEL;

SEL使用如下:

SEL func_sel = @selector(func);
[self performSelector:func_sel withObject:nil];

IMP

IMP:函数指针,即方法的地址。关于IMPruntime上是这样定义的。

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

使用如下:

IMP imp = class_getMethodImplementation(cls, sel);
imp(target, @selector(functionName),...参数)

Swizzle

exchangeImplementations

我们可以使用method_exchangeImplementations来交换两个SEL

 method_exchangeImplementations(oldMethod, newMethod);

我们来看下内部实现,其实就是交换了IMP

void method_exchangeImplementations(Method m1, Method m2) {
    //交换IMP
    IMP m1_imp = m1->imp;
    m1->imp = m2->imp;
    m2->imp = m1_imp;
    //省略...
}

使用起来也很简单。

#import <objc/runtime.h>

/// 交换Method
void exchangeMethod(Class aClass, SEL oldSEL, SEL newSEL) {
    Method oldMethod = class_getInstanceMethod(aClass, oldSEL);
    Method newMethod = class_getInstanceMethod(aClass, newSEL);
    method_exchangeImplementations(oldMethod, newMethod);
}


@implementation OCTest
+ (void)load {
    exchangeMethod([self class], @selector(description), @selector(hook_description));
}
- (NSString *)hook_description {
    NSLog(@"hook_description");
    //调用旧的IMP
    [self hook_description];
}

setImplementation

当然我们也可以替换原来MethodIMP,达到Swizzle的效果。

method_setImplementation(oldMethod, newImp);

我们来看下内部实现。

static IMP 
_method_setImplementation(Class cls, method_t *m, IMP imp) {
    //省略...
    IMP old = m->imp;
    //设置新的IMP
    m->imp = imp;
    //返回旧的IMP
    return old;
}

相比exchangeImplementations交换,使用起来稍微复杂了一点。

#import <objc/runtime.h>

/// 替换Method
void replaceMethod(Class hoolClass, SEL oldSEL, Class newClass, SEL newSEL, IMP *oldImp) {
    Method oldMethod = class_getInstanceMethod(hoolClass, oldSEL);
    IMP newImp = class_getMethodImplementation(newClass, newSEL);
    if (oldImp != NULL) {
        *oldImp = method_setImplementation(oldMethod, newImp);
    }
    else {
        method_setImplementation(oldMethod, newImp);
    }
}


@implementation OCTest
+ (void)load {
    //这里保留旧的IMP,在新的IMP里手动调用。
    IMP *old_description_ = &(old_description);
    replaceMethod([NSObject class], @selector(description), [self class], @selector(hook_description), old_description_);
}

/// 有返回值的IMP
typedef id (*BCIMP)(id, SEL, ...);
/// 无返回值的IMP
typedef void (*BCVIMP)(id, SEL, ...);
static IMP old_description = NULL;

- (NSString *)hook_description {
    NSLog(@"hook_description");
    NSString *result = nil;
    if (old_description != NULL) {
        result = ((BCIMP)old_description)(self, @selector(description));
    }
    return result;
}

因为我们是替换了MethodIMP,所以需要多一个静态变量保存old IMP,然后在执行new IMP的时候,手动调用old IMP

参考链接

opensource objc4

Tags: objc
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章