Method Swizzling
Contents
iOS AOP文章系列
前导知识:
AOP框架:
- Method Swizzling
- Fishhook
- Apsects
- NSProxy AOP
iOS AOP
接下来的文章我们正式进入 iOS AOP 的世界。
AOP 是一种面向切面编程,核心思想是在不修改源码情况下给程序添加功能的技术。
根据 OC 的 runtime 特性,我们先来介绍比较基础的一种实现方式 – Method Swizzling。
🌰 演示
我们依旧使用 前导知识文章 中的 Demo
,先为 Person
添加一个 Category
类来扩展一个 eat
方法,毕竟干饭人除了睡还要吃,要不和咸鱼有什么区别,我们想在 人 睡前先 吃饱饭,该如何利用 Swizzling
实现呢?
// Person.m
@implementation Person
- (void)sleep {
NSLog(@"i am sleeping");
}
@end
// Person+swizzle.m
@implementation Person (swizzle)
- (void)eat {
NSLog(@"i am eating");
// 这里调用自身并不会导致递归调用
// 因为在 main 中交换了 eat 和 sleep 的 IMP,所以这里实际上调用的是 sleep
[self eat];
}
@end
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
// 交换 sleep 和 eat 的 IMP
Method ori_Method = class_getInstanceMethod([Person class], @selector(sleep));
Method new_Method = class_getInstanceMethod([Person class], @selector(eat));
method_exchangeImplementations(ori_Method, new_Method);
[p sleep];
}
return 0;
}
这里调用 Person
的 sleep
方法,交换后实际调用 Person+swizzle
的 eat
方法,先打印 “i am eating”,然后执行 sleep
方法打印 “i am sleeping”。
当然还有一些在 runtime
中的 API
也能实现类似效果:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
// 类中添加方法IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
// 修改类方法Method class_getInstanceMethod(Class aClass, SEL aSelector)
// 获取类实力方法IMP method_setImplementation(Class cls, method_t *m, IMP imp)
// 设置方法 imp
原理
原理非常简单,我们看下 method_exchangeImplementations
的核心实现一目了然
void method_exchangeImplementations(Method m1, Method m2)
{
...
IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name();
m1->setImp(imp2);
m2->setImp(imp1);
...
}
就是在运行时,改变方法结构体 method_t
的底层实现
struct method_t {
SEL name; // 运行时方法名
const char *types; // 方法类型编码
MethodListIMP imp; // 方法实现的指针
}
执行的操作图形化为
使用建议
- +load
Swizzling
在类的 +load
方法中完成。
因为 +load
方法会在类被添加到 OC 运行时执行,且只会被调用一次,保证了 Swizzling
方法的及时处理。
- dispath_once
Swizzling
在 dispatch_once
中完成。保证只执行一次。
- prefix
Swizzling
方法添加前缀,避免方法名称冲突。
- invoke the original imp
不调用原始实现很可能会导致程序状态或逻辑异常。