如何对类方法进行 Method Swizzling

2017-01-14 10:01:41来源:http://www.jianshu.com/p/3d29278e5cf7作者:Cyandev人点击

第七城市

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 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));

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






第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台