个人写的一段代码,建议在了解Swizzling时,查看Runtime 以下方法源码
我为你准备好了
class_getInstanceMethod
class_addMethod
class_replaceMethod
method_exchangeImplementations
+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
Class msclass = cls;
// 获取当前类中的Org SEL (当前方法不存在,class_getInstanceMethod会去superClass查找)
Method originalMethod = class_getInstanceMethod(msclass, originalSelector);
// 获取当前类中的New SEL
Method swizzledMethod = class_getInstanceMethod(msclass, swizzledSelector);
// 尝试将Org SEL 添加到Class 中,IMP使用New SEL 的IMP,添加成功返回true,否则false(当前方法存在)
// class_addMethod 如果父类存在Org SEL,则会添加新方法,相当于重写父类Org SEL
BOOL didAddMethod =
class_addMethod(msclass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {·//添加成功,之前不存在 Org SEL
// 将New SEL的方法实现IMP 替换成 Org
class_replaceMethod(msclass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
//直接交换IMP的方法,如果父类存在Org,子类不存在,则会替换父类的Org SEL
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
1. Method Swizzling是什么
通常我们叫它方法交换
或方法欺骗
Method Swizzling
用于改变一个已经存在的selector
实现,在程序运行时,通过改变selector
和所在类的methodLists
的映射从而改变方法的调用。其实质就是交换两个方法的IMP(方法实现)
上篇文章我们知道:Method(方法)对应的是objc_Method结构体
,而objc_method结构体
中包含了SEL method_name(方法名)
和IMP(方法实现)
struct objc_method {
SEL _Nonnull method_name;
char * _Nullable method_types;
IMP _Nonnull method_imp;
};
Method(方法)
、SEL(方法名)
、IMP(方法实现)
三者的关系可以这样来表示:
在运行时,Class(类)
维护了一个method list(方法列表
)来确定消息的正确发送。method list(方法列表)
存放的元素就是Method(方法)
。而Method(方法)
中映射了一对键值对:SEL(方法名):IMP(方法实现)
。
Method Swizzling
修改了method list(方法列表)
,使得不同Method(方法)
中的键值对发生了交换。比如交换前两个键值对分别为SEL A :IMP A
、SEL B :IMP B
,交换之后就变成了SEL A :IMP B
、SEL B : IMP A
如图所示:

Swizzling 交换示意图
2. Method Swizzling 底层源码
(Runtime源码下载)[https://opensource.apple.com/tarballs/objc4/]
源码位于objc-class-old.m
void method_exchangeImplementations(Method m1_gen, Method m2_gen)
{
IMP m1_imp;
old_method *m1 = oldmethod(m1_gen);
old_method *m2 = oldmethod(m2_gen);
if (!m1 || !m2) return;
impLock.lock();
m1_imp = m1->method_imp;
m1->method_imp = m2->method_imp;
m2->method_imp = m1_imp;
impLock.unlock();
}
或 objc-runtime-new.m
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name();
m1->setImp(imp2);
m2->setImp(imp1);
// RR/AWZ updates are slow because class is unknown
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally?
flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
});
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
3. Method Swizzling使用方法
+ (void)swizzlingInClass:(Class)cls originalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
Class msclass = cls;
Method originalMethod = class_getInstanceMethod(msclass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(msclass, swizzledSelector);
BOOL didAddMethod =
class_addMethod(msclass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(msclass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
4. Method Swizzling 使用注意事项
- 应该只在 +load 中执行 Method Swizzling
程序在启动的时候,会先加载所有的类,这时会调用每个类的 +load 方法。而且在整个程序运行周期只会调用一次(不包括外部显示调用)。所以在 +load 方法进行 Method Swizzling 再好不过了。
而为什么不用+initialize
方法呢。
因为+initialize
方法的调用时机是在 第一次向该类发送第一个消息的时候才会被调用。如果该类只是引用,没有调用,则不会执行 +initialize
方法。
Method Swizzling
影响的是全局状态,+load
方法能保证在加载类的时候就进行交换,保证交换结果。而使用 +initialize
方法则不能保证这一点,有可能在使用的时候起不到交换方法的作用。
- Method Swizzling 在 +load 中执行时,不要调用 [super load];。
上边我们说了,程序在启动的时候,会先加载所有的类。如果在 + (void)load
方法中调用 [super load]
方法,就会导致父类的Method Swizzling
被重复执行两次,而方法交换也被执行了两次,相当于互换了一次方法之后,第二次又换回去了,从而使得父类的Method Swizzling
失效。
- Method Swizzling 应该总是在 dispatch_once 中执行
Method Swizzling 不是原子操作,dispatch_once 可以保证即使在不同的线程中也能确保代码只执行一次。所以,我们应该总是在 dispatch_once 中执行 Method Swizzling 操作,保证方法替换只被执行一次。