能够将Method Swizzling功能封装为类方法,做为NSObject的类别安全
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector; @end import "NSObject+Swizzling.h" @implementation NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; //原有方法 Method originalMethod = class_getInstanceMethod(class, originalSelector); //替换原有方法的新方法 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //先尝试給源SEL添加IMP,这里是为了不源SEL没有实现IMP的状况 BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { //添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { //添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换便可 method_exchangeImplementations(originalMethod, swizzledMethod); } } @end
在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键是这个方法的名字(SEL),值是指向这个方法实现的函数指针 implementation(IMP)多线程
伪代码表示: Class { MethodList ( Method{ SEL:IMP; } Method{ SEL:IMP; } ); };
Method Swizzling就是改变类的消息分发列表来让消息解析时从一个选择器(SEL)对应到另一个的实现(IMP),同时将原始的方法实现混淆到一个新的选择器(SEL)。ide
3.为何要添加didAddMethod判断函数
先尝试添加原SEL实际上是为了作一层保护,由于若是这个类没有实现originalSelector,但其父类实现了,那class_getInstanceMethod会返回父类的方法。这样method_exchangeImplementations替换的是父类的那个方法,这固然不是咱们想要的。因此咱们先尝试添加 orginalSelector,若是已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。
若是理解还不够透彻,咱们能够进入runtime.h中查看class_addMethod源码解释this
/** * Adds a new method to a class with a given name and implementation. * * @param cls The class to which to add a method. * @param name A selector that specifies the name of the method being added. * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd. * @param types An array of characters that describe the types of the arguments to the method. * * @return YES if the method was added successfully, otherwise NO * (for example, the class already contains a method implementation with that name). * * @note class_addMethod will add an override of a superclass's implementation, * but will not replace an existing implementation in this class. * To change an existing implementation, use method_setImplementation. */
大概的意思就是咱们能够经过class_addMethod为一个类添加方法(包括方法名称(SEL)和方法的实现(IMP)),返回值为BOOL类型,表示方法是否成功添加。spa
须要注意的地方是class_addMethod会添加一个覆盖父类的实现,但不会取代原有类的实现。也就是说若是class_addMethod返回YES,说明子类中没有方法originalSelector,经过class_addMethod为其添加了方法originalSelector,并使其实现(IMP)为咱们想要替换的实现线程
class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
同时再将原有的实现(IMP)替换到swizzledMethod方法上指针
class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
从而实现了方法的交换,而且未影响父类方法的实现。反之若是class_addMethod返回NO,说明子类中自己就具备方法originalSelector的实现,直接调用交换便可code
method_exchangeImplementations(originalMethod, swizzledMethod);
在Objective-C runtime会自动调用两个类方法,分别为+load与+ initialize。 +load 方法是在类被加载的时候调用的,也就是必定会被调用。 +initialize方法是在类或它的子类收到第一条消息以前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说+initialize方法是以懒加载的方式被调用的,若是程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是永远不会被调用的。 此外+load方法还有一个很是重要的特性,那就是子类、父类和分类中的+load方法的实现是被区别对待的。换句话说在 Objective-C runtime自动调用+load方法时,分类中的+load方法并不会对主类中的+load方法形成覆盖。综上所述,+load 方法是实现 Method Swizzling 逻辑的最佳“场所” 详细请看:http://www.jianshu.com/p/872447c6dc3f
方法交换应该要线程安全,并且保证在任何状况下(多线程环境,或者被其余人手动再次调用+load方法)只交换一次,防止再次调用又将方法交换回来。除非只是临时交换使用,在使用完成后又交换回来。 最经常使用的解决方案是在+load方法中使用dispatch_once来保证交换是安全的。以前有读者反馈+load方法自己即为线程安全,为何仍需添加dispatch_once,其缘由就在于+load方法自己没法保证其中代码只被执行一次