Runtime中的Method Swizzling

runtime中的Method Swizzling(AOP)

一、动态的为类注入新的方法

使用场景:拦截系统方法 eg: viewVillAppear,viewDidLoad,imageNamed....
使用到的方法

- 添加方法
/**
 * cls 被添加方法所在的类
 * name 添加的方法名字
 * IMP    实现这个方法的函数
 * types  一个定义函数返回类型和参数的类型的字符串 (常用method_getTypeEncoding(method)获取)
 */
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

- 替换方法的实现
class_replaceMethod(Class cls, SEL name, IMP imp,  const char *types) 

- 交换两个方法的实现
method_exchangeImplementations(Method m1, Method m2)     

- 获得某个类的类方法
Method class_getClassMethod(Class cls , SEL name)

- 获得某个类的实例对象方法
Method class_getInstanceMethod(Class cls , SEL name) 
                    

其本质无非就是改变方法选择器指针的指向(改变指针的地址)
eg:AOP中拦截一个方法的实现

// originalSelector  原有方法
// swizzledSelector 新的方法
+ (void)swizzledMethod:(SEL)originalSelector and:(SEL)swizzledSelector
{
  // 获取当前类
  Class currentClass = [self class];
  // 当前方法选择器指向的实例方法
  Method originalMethod = class_getInstanceMethod(currentClass, originalSelector);
  Method swizzledMethod = class_getInstanceMethod(currentClass, swizzledSelector);
  
  // 为当前类添加一个方法(如果当前类中已经存在originalSelector,返回false,否则返回true)
  BOOL didAddMethod =
  class_addMethod(currentClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
  // 方法添加成功
  if (didAddMethod) {
      class_replaceMethod(currentClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
  } else {// 如果当前类中已经存在originalSelector 直接交换两个方法的实现
      method_exchangeImplementations(originalMethod, swizzledMethod);
  }
}

选择器、方法与实现

Runtime中的Method Swizzling_第1张图片
680076-b00b5b0a1a633428.png

在Objective-C中,选择器(selector)、方法(method)和实现(implementation)是运行时中一个特殊点,虽然在一般情况下,这些术语更多的是用在消息发送的过程描述中。

以下是Objective-C Runtime Reference中的对这几个术语一些描述:

Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。
Method(typedef struct objc_method Method):在类定义中表示方法的类型
Implementation(typedef id (
IMP)(id, SEL, …)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。
理解这几个术语之间的关系最好的方式是:一个类维护一个运行时可接收的消息分发表;分发表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个实现(IMP),即指向底层C函数的指针。

为了swizzle一个方法,我们可以在分发表中将一个方法的现有的选择器映射到不同的实现,而将该选择器对应的原始实现关联到一个新的选择器中。

注意细节
Swizzling应该总是在+load中执行

在Objective-C中,运行时会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。+load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证—事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用。

Swizzling应该总是在dispatch_once中执行

与上面相同,因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。

你可能感兴趣的:(Runtime中的Method Swizzling)