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
指针,你可能会问这是什么意思呢?是时候祭出这张图了:
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));
就是这么简单!看下效果: