如何对类方法进行 Method Swizzling

Method Swizzling 应该是很多开发者都非常熟悉并且经常接触的技术,也是 Objective-C Runtime 的一大特色,但是很多人在使用的时候也许只做过对实例方法进行 swizzling,但是对于类方法也就是加号方法,常用的手段却不能实现。

我曾经搜索过有关如何对类方法进行 Method Swizzling 的方法,但是相关的问题非常少,没有直接解决,索性从 Runtime 源码开始入手,研究这个问题。

在开始之前,先放出本次的 Demo 代码:

static void SwizzleMethod(Class cls, SEL ori, SEL rep) {
    Method oriMethod = class_getInstanceMethod(cls, ori);
    Method repMethod = class_getInstanceMethod(cls, rep);
    
    BOOL flag = class_addMethod(cls, ori, method_getImplementation(repMethod), method_getTypeEncoding(repMethod));
    
    if (flag) {
        class_replaceMethod(cls, rep, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    } else {
        method_exchangeImplementations(oriMethod, repMethod);
    }
}

@interface Foo : NSObject

+ (void)bar;

@end

@implementation Foo

+ (void)bar {
    NSLog(@"[Foo bar] called!!");
}

@end


@implementation Foo (Swizzle)

+ (void)swz_bar {
    NSLog(@"Before calling ----");
    [self swz_bar];
    NSLog(@"After calling ----");
}

@end

最终要实现对 Foo 的类方法 bar 进行重新调配,我的切入点放到了 class_getInstanceMethod 这个 Runtime 方法上,这个函数大体做了以下的工作:检查参数,从缓存里查找,如果没找到再从方法列表中查找。其实,Runtime 还提供了一个 class_getClassMethod 函数,用来获取类方法,它的源码如下:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

可以看到,它实际上还是调用了 class_getInstanceMethod,只不过 cls 参数变成了 cls->getMeta(),继续跟踪:

Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

最终它返回了一个 Class 对象的 isa 指针,你可能会问这是什么意思呢?是时候祭出这张图了:

如何对类方法进行 Method Swizzling_第1张图片
Objective-C Class Diagram

Objective-C 的类型系统设计也是十分巧妙,我们可以通过 object_getClass 这个方法获取到一个实例的类对象,其实它走的就是 isa 指针,假设我们已经得到 Foo 的类对象了(如图中间一列所示),我们再次使用 object_getClass 就可以得到最右列的 Meta-Class 了,所以 Meta-Class 又是什么呢?

Objective-C 中使用 id 类型来表示一个实例对象,id 实质就是 objc_object *typedef,一个实例对象的结构体里存储了自己 ivar 的值和一些其他的信息,而它的实例方法都存在于中间那一列 objc_class 对象中。当我们使用 [foo bar] 时,Runtime 会通过 foo->isa 找到 objc_class,然后在里面找到相应方法调用。但如果我们使用 [Foo bar],此时的 receiver 是一个 objc_class,Runtime 同样会顺着 isa 指针找,最终找到了 Meta-Class,自然地,类方法就在那里面。

所以,回到咱们的问题上,如何对类方法进行 Method Swizzling,自然就是对一个类的 Meta-Class 进行操作即可。我们看一下操作实例方法的代码:

SwizzleMethod([Foo class], @selector(bar), @selector(swz_bar));

那么要操作类方法,就是把 cls 参数变成这个类的 Meta-Class 即可:

SwizzleMethod(object_getClass([Foo class]), @selector(bar), @selector(swz_bar));

就是这么简单!看下效果:


如何对类方法进行 Method Swizzling_第2张图片

你可能感兴趣的:(如何对类方法进行 Method Swizzling)