AOP 思想
AOP:Aspect Oriented Programming,译为面向切面编程,是可以通过预编译的方式和运行期动态实现,在不修改源代码的情况下,给程序动态统一添加功能的技术。
面向对象编程(OOP)适合定义从上到下的关系,但不适用于从左到右,计算机中任何一门新技术或者新概念的出现都是为了解决一个特定的问题的,我们看下AOP解决了什么样的问题。
例如一个电商系统,有很多业务模块的功能,使用OOP来实现核心业务是合理的,我们需要实现一个日志系统,和模块功能不同,日志系统不属于业务代码。如果新建一个工具类,封装日志打印方法,再在原有类中进行调用,就增加了耦合性,我们需要从业务代码中抽离日志系统,然后独立到非业务的功能代码中,这样我们改变这些行为时,就不会影响现有业务代码。
当我们使用各种技术来拦截方法,在方法执行前后做你想做的事,例如日志打印,就是所谓的AOP。
主流的AOP 方案
Method Swizzle
说到iOS中AOP的方案第一个想到的应该就是 Method Swizzle
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:413038000,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
得益于Objective-C这门语言的动态性,我们可以让程序在运行时做出一些改变,进而调用我们自己定义的方法。使用Runtime 交换方法的核心就是:method_exchangeImplementations
, 它实际上将两个方法的实现进行交换:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
SEL originalSelector = @selector(method_original:);
SEL swizzledSelector = @selector(method_swizzle:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
BOOL didAddMethod = class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
复制代码
作为我们常说的黑魔法 Method Swizzle 到底危险不危险,有没有最佳实践。
这里可以通过这篇回答一起深入理解下。这里列出了一些 Method Swizzling 的陷阱:
- Method swizzling is not atomic
你会把 Method Swizzling 修改方法实现的操作放在一个加号方法 +(void)load
里,并在应用程序的一开始就调用执行,通常放在 dispatch_once()
里面来调用。你绝大多数情况将不会碰到并发问题。
- Changes behavior of un-owned code
这是 Method Swizzling 的一个问题。我们的目标是改变某些代码。当你不只是对一个UIButton类的实例进行了修改,而是程序中所有的UIButton实例,对原来的类侵入较大。
- Possible naming conflicts
命名冲突贯穿整个 Cocoa 的问题. 我们常常在类名和类别方法名前加上前缀。不幸的是,命名冲突仍是个折磨。但是swizzling其实也不必过多考虑这个问题。我们只需要在原始方法命名前做小小的改动来命名就好,比如通常我们这样命名:
@interface UIView : NSObject
- (void)setFrame:(NSRect)frame;
@end
@implementation UIView (MyViewAdditions)
- (void)my_setFrame:(NSRect)frame {
// do custom work
[self my_setFrame:frame];
}
+ (void)load {
[self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];
}
@end
复制代码
这段代码运行是没问题的,但是如果 my_setFrame
: 在别处被定义了会发生什么呢?比如在别的分类中,当然这个问题不仅仅存在于swizzling 中,其他地方也可能会出现,这里可以有个变通的方法,利用函数指针来定义
@implementation UIView (MyViewAdditions)
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];
}
@end
复制代码
- Swizzling changes the method’s arguments
我认为这是最大的问题。想正常调用 Method Swizzling 的方法将会是个问题。比如我想调用 my_setFrame
:
[self my_setFrame:frame];
复制代码
Runtime 做的是 objc_msgSend(self, @selector(my_setFrame:), frame); Runtime去寻找my_setFrame
:的方法实现,但因为已经被交换了,事实上找到的方法实现是原始的 setFrame
: 的,如果想调用 Method Swizzling 的方法,可以通过上面的函数的方式来定义,不走Runtime 的消息发送流程。不过这种需求场景很少见。
- The order of swizzles matters
多个swizzle方法的执行顺序也需要注意。假设 setFrame
: 只定义在 UIivew 中,想像一下按照下面的顺序执行:
[UIView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[UIControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame