Runtime && Method Swizzling

一、Method Swizzling通用方法封装

能够将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

2.SEL、Method、IMP的含义及区别

在运行时,类(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);

 

2.方法交换调用在+load方法中

在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

3.方法交换要在dispatch_once中执行

方法交换应该要线程安全,并且保证在任何状况下(多线程环境,或者被其余人手动再次调用+load方法)只交换一次,防止再次调用又将方法交换回来。除非只是临时交换使用,在使用完成后又交换回来。 最经常使用的解决方案是在+load方法中使用dispatch_once来保证交换是安全的。以前有读者反馈+load方法自己即为线程安全,为何仍需添加dispatch_once,其缘由就在于+load方法自己没法保证其中代码只被执行一次
相关文章
相关标签/搜索