Contents

Method Swizzling

iOS AOP文章系列

前导知识:

AOP框架:

iOS AOP

接下来的文章我们正式进入 iOS AOP 的世界。

AOP 是一种面向切面编程,核心思想是在不修改源码情况下给程序添加功能的技术。

根据 OC 的 runtime 特性,我们先来介绍比较基础的一种实现方式 – Method Swizzling

🌰 演示

我们依旧使用 前导知识文章 中的 Demo,先为 Person 添加一个 Category 类来扩展一个 eat 方法,毕竟干饭人除了睡还要吃,要不和咸鱼有什么区别,我们想在 人 睡前先 吃饱饭,该如何利用 Swizzling 实现呢?

// Person.m
@implementation Person

- (void)sleep {
    NSLog(@"i am sleeping");
}

@end

// Person+swizzle.m
@implementation Person (swizzle)

- (void)eat {
    NSLog(@"i am eating");
    // 这里调用自身并不会导致递归调用
    // 因为在 main 中交换了 eat 和 sleep 的 IMP,所以这里实际上调用的是 sleep
    [self eat];
}

@end

// main.m
int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        
        // 交换 sleep 和 eat 的 IMP
        Method ori_Method = class_getInstanceMethod([Person class], @selector(sleep));
        Method new_Method = class_getInstanceMethod([Person class], @selector(eat));
        method_exchangeImplementations(ori_Method, new_Method);
        
        [p sleep];
    }
    return 0;
}

这里调用 Personsleep 方法,交换后实际调用 Person+swizzleeat 方法,先打印 “i am eating”,然后执行 sleep 方法打印 “i am sleeping”。

当然还有一些在 runtime 中的 API 也能实现类似效果:

  • BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) // 类中添加方法
  • IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) // 修改类方法
  • Method class_getInstanceMethod(Class aClass, SEL aSelector) // 获取类实力方法
  • IMP method_setImplementation(Class cls, method_t *m, IMP imp) // 设置方法 imp

原理

原理非常简单,我们看下 method_exchangeImplementations 的核心实现一目了然

void method_exchangeImplementations(Method m1, Method m2)
{
    ...
    IMP imp1 = m1->imp(false);
    IMP imp2 = m2->imp(false);
    SEL sel1 = m1->name();
    SEL sel2 = m2->name();

    m1->setImp(imp2);
    m2->setImp(imp1);
    ...
}

就是在运行时,改变方法结构体 method_t 的底层实现

struct method_t {
    SEL name; // 运行时方法名
    const char *types; // 方法类型编码
    MethodListIMP imp; // 方法实现的指针
}

执行的操作图形化为

使用建议

  • +load

Swizzling 在类的 +load 方法中完成。

因为 +load 方法会在类被添加到 OC 运行时执行,且只会被调用一次,保证了 Swizzling 方法的及时处理。

  • dispath_once

Swizzlingdispatch_once 中完成。保证只执行一次。

  • prefix

Swizzling 方法添加前缀,避免方法名称冲突。

  • invoke the original imp

不调用原始实现很可能会导致程序状态或逻辑异常。