个人写的一段代码,建议在了解Swizzling时,查看Runtime 以下方法源码
我为你准备好了
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 操作,保证方法替换只被执行一次。