方法交换

1.方法交换

Method Swizzling

作用:动态交换两个方法的实现(即IMP指针);

原理:在类的结构体中有一个methodLists,它存放着 SEL 与 IMP 的映射关系。Method Swizzling 就是对这个列表进行的操作,通过改变这种映射关系,完成两个方法IMP的交换;

使用场景:

  1. 类的原有方法无法满足新需求,需要扩展并替换。如数组新增对象时,检测对象是否为nil;
  2. 新增页面统计等功能时,需新建父类实现统计业务,再让相关类继承此类,这会造成重复劳动。如果在运行时事先替换各viewDidLoad为自己的统计方法,则只需要一次操作即可;

其核心代码是runtime的下面两个C语言API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/** 
* Exchanges the implementations of two methods.
*
* @param m1 Method to exchange with second method.
* @param m2 Method to exchange with first method.
*
* @note This is an atomic version of the following:
* \code
* IMP imp1 = method_getImplementation(m1);
* IMP imp2 = method_getImplementation(m2);
* method_setImplementation(m1, imp2);
* method_setImplementation(m2, imp1);
* \endcode
*/
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)


/**
* Returns a specified instance method for a given class.
*
* @param cls The class you want to inspect.
* @param name The selector of the method you want to retrieve.
*
* @return The method that corresponds to the implementation of the selector specified by
* \e name for the class specified by \e cls, or \c NULL if the specified class or its
* superclasses do not contain an instance method with the specified selector.
*
* @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.
*/
OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)

2.应用

数组新增对象时检测nil对象,防止闪退。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)

+(void)load{
[self swizzle_install];
}

+ (void)swizzle_install
{
Method method1 = class_getInstanceMethod(objc_getClass("__NSArrayM"),
@selector(addObject:));
Method method2 = class_getInstanceMethod(objc_getClass("__NSArrayM"),
@selector(swizzle_addObject:));
// 交换两个方法的实现函数
method_exchangeImplementations(method1, method2);
}

-(void)swizzle_addObject:(id)anObject
{
// 发现空对象
if (nil == anObject)
{
@try {
// 注意:因为已经交换过方法了,所以这里调用的是数组的addObject:方法。
[self swizzle_addObject:anObject];
} @catch (NSException *exception) {
// 捕获异常,打印日志,避免闪退。
NSLog(@"Crash reason:\n %@", [exception callStackSymbols]);
} @finally {}
}
// 如果对象不为空
else{
// 正常调用数组原生的addObject:方法,不捕获异常。
[self swizzle_addObject:anObject];
}
}
@end

在APP启动后,运行时会先执行 NSMutableArray 分类中的 +load 函数,这里会调用swizzle_install,启动方法交换:

1
2
3
4
5
6
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//添加对象
NSMutableArray *array = [NSMutableArray new];
[array addObject:nil];
}

上面代码中,虽然向数组中加入nil对象,但因为已经对addObject方法做了交换并做了容错处理,所以只是会打印崩溃日志,应用并不会闪退。


方法交换
https://davidlii.cn/2017/09/01/runtime-swizzle.html
作者
Davidli
发布于
2017年9月1日
许可协议