iOS AOP 方案的对比与思考

AOP 思想

AOP:Aspect Oriented Programming,译为面向切面编程,是可以通过预编译的方式和运行期动态实现,在不修改源代码的情况下,给程序动态统一添加功能的技术。

面向对象编程(OOP)适合定义从上到下的关系,但不适用于从左到右,计算机中任何一门新技术或者新概念的出现都是为了解决一个特定的问题的,我们看下AOP解决了什么样的问题。

例如一个电商系统,有很多业务模块的功能,使用OOP来实现核心业务是合理的,我们需要实现一个日志系统,和模块功能不同,日志系统不属于业务代码。如果新建一个工具类,封装日志打印方法,再在原有类中进行调用,就增加了耦合性,我们需要从业务代码中抽离日志系统,然后独立到非业务的功能代码中,这样我们改变这些行为时,就不会影响现有业务代码。

当我们使用各种技术来拦截方法,在方法执行前后做你想做的事,例如日志打印,就是所谓的AOP。

主流的AOP 方案

Method Swizzle

说到iOS中AOP的方案第一个想到的应该就是 Method Swizzle

得益于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 的陷阱:

(1)Method swizzling is not atomic

你会把 Method Swizzling 修改方法实现的操作放在一个加号方法 +(void)load 里,并在应用程序的一开始就调用执行,通常放在 dispatch_once() 里面来调用。你绝大多数情况将不会碰到并发问题。

(2)Changes behavior of un-owned code

这是 Method Swizzling 的一个问题。我们的目标是改变某些代码。当你不只是对一个UIButton类的实例进行了修改,而是程序中所有的UIButton实例,对原来的类侵入较大。

(3)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

(4)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 的消息发送流程。不过这种需求场景很少见。

(5)The order of swizzles matters

多个swizzle方法的执行顺序也需要注意。假设 setFrame: 只定义在 UIivew 中,想像一下按照下面的顺序执行:

[UIView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
[UIControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];
[UIButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];

这里需要注意的是swizzle的顺序,多个有继承关系的类的对象swizzle时,先从父对象开始。 这样才能保证子类方法拿到父类中的被swizzle的实现。在+(void)load中swizzle不会出错,就是因为load类方法会默认从父类开始调用,不过这种场景很少,一般会选择一个类进行swizzle。

(6)Difficult to understand (looks recursive)

新方法的实现里面会调用自己同名的方法,看起来像递归,但是看看上面已经给出的 swizzling 封装方法, 使用起来就很易读懂,这个问题是已完全解决的了!

(7)Difficult to debug

调试时不管通过bt 命令还是 [NSThread callStackSymbols] 打印调用栈,其中掺杂着被swizzle的方法名,会显得一团槽!上面介绍的swizzle方案&#

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值