盒子
盒子
Posts List
  1. 前言
  2. Method Swizzling原理
  3. Method Swizzling使用
  4. 总结

iOS Runtime-Method Swizzling

作者Talent•C
转载请注明出处

前言

这几天暂时比较闲,翻翻博客看一下,发现一年没有更新了,很惭愧啊~~
说一个工程中有一个很有意思的代码现象吧,使用 Runtimemethod_exchangeImplementations 函数将 UIViewControllerviewDidLoad 函数 与 myViewDidLoad 函数交换后在其内部调用 [self myViewDidLoad];,看到这个代码难道不会出现递归调用吗? 带这个这个疑问我们来看一下其原因吧。
代码示例:

1
2
3
- (void)myViewDidLoad {
[self myViewDidLoad];
}

Method Swizzling原理

Method Swizzing 是发生在运行时的,主要用于在运行时将两个 Method 进行交换,我们可以将 Method Swizzling 代码写到任何地方,但是只有在这段 Method Swilzzling 代码执行完毕之后互换才起作用。

而且 Method Swizzling 也是iOSAOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。
首先,让我们通过两张图片来了解一下 Method Swizzling 的实现原理
未做方法交换:
s

方法交换后:

OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。

在每个类中都有一个 Dispatch Table,这个 Dispatch Table 本质是将类中的SELIMP(可以理解为函数指针)进行对应。而我们的 Method Swizzling 就是对这个 table 进行了操作,让SEL对应另一个IMP

Method Swizzling使用

在实现 Method Swizzling 时,核心代码主要就是一个runtimeC语言API

1
2
OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

了解完原理后我们具体操作一下 创建一个iOS工程,并给 UIViewController 添加一个Category,然后在Category中的 +(void)load 方法中添加 Method Swizzling 方法,我们用来替换的方法也写在这个Category中。由于 +load 类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用。而且这个方法具有唯一性,也就是只会被调用一次,不用担心资源抢夺的问题。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "UIViewController+sw.h"
#import @implementation UIViewController (sw)
+ (void)load {
[super load];
Method method1 = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method method2 = class_getInstanceMethod([self class], @selector(myViewDidLoad));
/**
* 我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
* 而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
* 所以我们在这里通过class_addMethod()的验证。
*/
if (!class_addMethod([self class], @selector(viewDidLoad), method_getImplementation(method2), method_getTypeEncoding(method2))) {
method_exchangeImplementations(method1, method2);
}
}
// 我们自己实现的方法,也就是和self的viewDidLoad方法进行交换的方法。
- (void)myViewDidLoad {
[self myViewDidLoad];
NSLog(@"myViewDidLoad");
}
@end

看到这里就出现了文章开头的问题了, myViewDidLoad 中调用自己会不会出现递归调用???
答案是当然不会的, 通过上面两个图就可以看出原因来,Method Swizzling 的实现原理可以理解为 ”方法互换“。假设我们将A和B两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。

例如我们上面的代码,系统调用 UIViewControllerviewDidLoad 方法时,实际上执行的是我们实现的 myViewDidLoad 方法。而我们在 myViewDidLoad 方法内部调用 [self myViewDidLoad]; 时,执行的是 UIViewControllerviewDidLoad 方法。所以并不会出现递归调用。
我们将上面的 myViewDidLoad 函数实现修改一下,按照上面的解释应该就会出现递归调用,我们来验证一下:

1
2
3
4
- (void)myViewDidLoad {
[self viewDidLoad];
NSLog(@"myViewDidLoad");
}

运行程序会发现程序启动后卡住了,几秒后程序崩溃了,在调用堆栈里看到出现了方法递归调用。

关于 Method Swizzling 的用处有很多,这里给大家推荐一个Github上星星最多的一个第三方库-JRSwizzle

总结

天下难事,必做于易;天下大事,必做于细。 —— 老子

支持一下
扫一扫,支持Talent•C