前言
最近在搞重构相关的事情,遇到了不少这样的场景:
进入一个界面,在
viewWillAppear:
的时候做相应判断,如果满足条件则执行对应代码。
这类业务有一个特点,业务内容是对应整个App的,与对应的ViewController
毛关系都没有,但是却不得不耦合到(即使是调用代码可以精简到一行)ViewController
中。
我们都知道,这种类似的业务用AOP(面向切片编程)来做十分适合,所谓面向切片编程就是在不修改原方法的前提下,动态的插入自己的想要的执行代码,由于Objective C是动态语言,可以很容易的利用method swizzling来实现AOP。
在正文之前特别感谢微信阅读团队的这篇博客:
这篇博客原理上讲解的比较清楚,但是细节上并没有讲的很详细,所以也就有了本文。
Objective C方法调用过程
这个其实我之前在这篇博客里讲过:
这里,把核心的内容再一次列出来。
如下Objective C代码
- (NSInteger )myTestFunction:(NSInteger)input{
return input + 1;
}
- (void)mySpecialFunction{
NSInteger result = [self myTestFunction:10];
}
用clang来重写为C++,
clang -rewrite-objc MyClass.m
然后,我们通过搜索mySpecialFunction
方法名字,来找到转换后的代码,经过简单整理如下
static NSInteger _I_MyClass_myTestFunction_(MyClass * self, SEL _cmd, NSInteger input) {
return input + 1;
}
static void _I_MyClass_mySpecialFunction(MyClass * self, SEL _cmd) {
NSInteger result = objc_msgSend(self, sel_registerName("myTestFunction:"),10);
}
我们看到,方法体进行了如下转换
//OC
- (NSInteger )myTestFunction:(NSInteger)input{
return input + 1;
}
//C++
static NSInteger _I_MyClass_myTestFunction_(MyClass * self, SEL _cmd, NSInteger input) {
return input + 1;
}
方法调用进行了如下转换
//OC
NSInteger result = [self myTestFunction:10];
//C++
NSInteger result = objc_msgSend(self, sel_registerName("myTestFunction:"),10);
不难看出,方法的调用并不是直接转换成了对应的C/C++方法调用,而是调用了objc_msgSend
通过SEL
(就是一个字符串)在运行时动态找到这个的执行体_I_MyClass_myTestFunction_
。
那么,在运行时如何找到这个方法的执行体呢? 这里省略一些细节,对细节感兴趣的同学可以看我上文写的那篇文章。一个实例方法的流程如下:
- 对象实例收到消息(SEL+参数)
- 根据存储在对象实例中的ISA到类对象,类对象依次查找Class Cache(方法表缓存)和dispatch table找到对应的Method,如果找到Method,执行对应Method的IMP(方法体),并且返回结果
- 如果找不到Method,则根据类对象中的
super_class
指针找到父类的Class对象。一直找到NSObject
的类对象 - 如果NSObject也无法找到这个SEL,则进入消息转发机制
- 如果消息转发机制无法处理,则抛出异常:
doesNotRecognizeSelector
Method Swizzling
通过上文我们知道,一个方法的调用实际上就是SEL(方法名)通过Runtime找到IMP(方法执行体)