# 05.MethodSwizzling的问题

> 使用 Method Swizzling 编程就好比切菜时使用锋利的刀，一些人因为担心切到自己所以害怕锋利的刀具，可是事实上，使用钝刀往往更容易出事，而利刀更为安全。 Method swizzling 可以帮助我们写出更好的，更高效的，易维护的代码。但是如果滥用它，也将会导致难以排查的bug。

## 相关方法

*   `class_replaceMethod` 替换类方法的定义

    &#x20; \*&#x20;
* `method_exchangeImplementations` 交换 2 个方法的实现
  * 这里相当于调用了两次`method_setImplementation`
  * 点进方法的注释里面可以看到相关注释
  * 即 A <-> B
* `method_setImplementation` 设置 1 个方法的实现
  * 即 A -> B

> 注意点: 用`method_setImplementation`的话单方面设置实现有的场景挺实用 `functionA` 设置了 `functionB`的实现 那么项目中使用 `functionA` 和 `functionB` 的地方就会有同样的实现 但是如果用 `method_exchangeImplementations` 的话,就会有不同的实现

## 看一下 RNSwizzle 的封装

```
//
//  RNSwizzle.m
//  MethodSwizzle

#import "RNSwizzle.h"
#import <objc/runtime.h>

@implementation NSObject (RNSwizzle)

+ (IMP)swizzleSelector:(SEL)origSelector 
               withIMP:(IMP)newIMP {
  Class class = [self class];
  Method origMethod = class_getInstanceMethod(class,
                                              origSelector);
  IMP origIMP = method_getImplementation(origMethod);

  if(!class_addMethod(self, origSelector, newIMP,
                      method_getTypeEncoding(origMethod)))
  {
    method_setImplementation(origMethod, newIMP);
  }

  return origIMP;
}
```

## 方法交换不是自动的

我所见过的使用method swizzling实现的方法在并发使用时基本都是安全的。95%的情况里这都不会是个问题。通常你替换一个方法的实现，是希望它在整个程序的生命周期里有效的。也就是说，你会把 method swizzling 修改方法实现的操作放在一个加号方法 +(void)load里，并在应用程序的一开始就调用执行。你将不会碰到并发问题。假如你在 +(void)initialize初始化方法中进行swizzle，那么……rumtime可能死于一个诡异的状态。

## 改变不是自己的代码

这是swizzling的一个问题。我们的目标是改变某些代码。swizzling方法是一件灰常灰常重要的事，当你不只是对一个NSButton类的实例进行了修改，而是程序中所有的NSButton实例。因此在swizzling时应该多加小心，但也不用总是去刻意避免。

想象一下，如果你重写了一个类的方法，而且没有调用父类的这个方法，这可能会引起问题。大多数情况下，父类方法期望会被调用（至少文档是这样说的）。如果你在swizzling实现中也这样做了，这会避免大部分问题。还是调用原始实现吧，如若不然，你会费很大力气去考虑代码的安全问题。

## 命名冲突

命名冲突贯穿整个Cocoa的问题. 我们常常在类名和类别方法名前加上前缀。不幸的是，命名冲突仍是个折磨。但是swizzling其实也不必过多考虑这个问题。我们只需要在原始方法命名前做小小的改动来命名就好，比如通常我们这样命名

```
+ (void)load {
    [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}
```

这段代码运行正确，但是如果my\_setFrame: 在别处被定义了会发生什么呢？ 这个问题不仅仅存在于swizzling，这里有一个替代的变通方法：

```
static void MySetFrame(id self, SEL _cmd, NSRect frame);
static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);

static void MySetFrame(id self, SEL _cmd, NSRect frame) {
    // do custom work
    SetFrameIMP(self, _cmd, frame);
}

+ (void)load {
    [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];
    }
```

看起来不那么Objectice-C了（用了函数指针），这样避免了selector的命名冲突。 最后给出一个较完美的swizzle方法的定义：

```
typedef IMP *IMPPointer;

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) { *store = imp; }
    return (imp != NULL);
}

@implementation NSObject (FRRuntimeAdditions)

+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
    return class_swizzleMethodAndStore(self, original, replacement, store);
}
@end
```

## 交换方法影响了方法调用

项目中调用不同方法名的问题 上面讨论过了

## 交换的顺序

* 多个有继承关系的类的对象swizzle时，先从父对象开始
* 这样才能保证子类方法拿到父类中的被swizzle的实现
* 在+(void)load中swizzle不会出错，就是因为load类方法会默认从父类开始调用
