iOS中的“面向切面”式编程

来源:互联网 发布:大漠骑兵网络 编辑:程序博客网 时间:2024/09/21 09:20

http://suenblog.duapp.com/blog/100010/iOS%E4%B8%AD%E7%9A%84%E2%80%9C%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2%E2%80%9D%E5%BC%8F%E7%BC%96%E7%A8%8B

iOS中的“面向切面”式编程

AOP编程这种概念,很少在iOS客户端这里提起过,简单的解释一下:AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

我们先来看一下iOS这种“动态代理”的实现方式,有关NSProxy的部分,我就不多说了,大家可以参考一下这篇博客 iOS的动态代理模式的实现。
我们来学习另一种方法,使用runtime's method-swizzle来实现
先介绍几个方法

[methodExchange]
12345678910111213141516
/**  * Exchanges the implementations of two methods. *  * @param m1 Method to exchange with second method. * @param m2 Method to exchange with first method. *  * @note This is an atomic version of the following: *  \code  *  IMP imp1 = method_getImplementation(m1); *  IMP imp2 = method_getImplementation(m2); *  method_setImplementation(m1, imp2); *  method_setImplementation(m2, imp1); *  \endcode */OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)      __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

这个方法主要是用于交换两个方法的实现

[methodObtain]
1234567891011121314
/**  * Returns a specified instance method for a given class. *  * @param cls The class you want to inspect. * @param name The selector of the method you want to retrieve. *  * @return The method that corresponds to the implementation of the selector specified by  *  \e name for the class specified by \e cls, or \c NULL if the specified class or its  *  superclasses do not contain an instance method with the specified selector. * * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not. */OBJC_EXPORT Method class_getInstanceMethod(Class cls, SEL name)    __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

这个主要是根据class名称和selector得到Method
有了上面的两个方法,我们就可以交换某个class下的某个方法的实现,当然,方法不一定出自同一个class。 我们来直接上代码,比如我们想把UIView 的initWithFrame:方法交换成我们自定义的一个方法st_initWithFrame:,我们只需要得到两个方法的实现,然后调用 method_exchangeImplementations 方法即可。为了方便,我就直接把替换的过程写在load方法里面了,如下代码所示

[UIView+swizzleInitialize]
12345678910111213141516
@interface UIView (STSwizzle)- (instancetype) st_initWithFrame:(CGRect) frame;@end@implementation UIView (STSwizzle)- (instancetype) st_initWithFrame:(CGRect) frame {    NSLog(@"===before invoke initWithFrame==");    id instance = [self st_initWithFrame:frame];    NSLog(@"===after invoke initWithFrame==");    return instance;}@end

我们在使用前交换这两个方法就可以了,注意,这个交换是一个全局的,你这里交换了之后,其他地方使用的过程中也会被交换。交换代码如下

[UIViewTest]
123456789101112131415161718192021222324252627
@interface ViewController@end@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.        method_exchangeImplementations(class_getInstanceMethod(UIView.class, @selector(initWithFrame:)), class_getInstanceMethod(UIView.class, @selector(st_initWithFrame:)));        UIView * view = [[UIView alloc] initWithFrame:self.view.bounds];    view.backgroundColor = UIColor.redColor;    [self.view addSubview:view];}- (void)didReceiveMemoryWarning {    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.}@end

上述的代码就完成了两个方法的交换,留心观察的同学会发现,我在自定义的st_initWithFrame:方法里面又调用了一次方法自身,从静态代码来看,像是死循环,我可以很负责的告诉大家,#### 上述代码如果未发生交换,确实是死循环,但是我们在后面对方法进行了交换,运行期间,我们方法内执行的st_initWithFrame其实已经被交换成原来的initWithFrame:方法了,虚惊一场,大家不要担心,可以自己多做做实验。如果有人运行了例子之后,就可以看到打印的日志如下

[打印的日志]
12
2014-10-23 17:10:40.542 STAOPDemo[17792:816954] ===before invoke initWithFrame==2014-10-23 17:10:40.543 STAOPDemo[17792:816954] ===after invoke initWithFrame==

这说明我们确实交换了方法,并且也初步看到了一种面向切面的新思路,我们没有改变调用方式以及原有提供的接口方法,我们只是新增了category,并且交换了方法而已,绿色无公害,不需要修改原有代码,我们可以把上述交换过程的代码完全放到UIView中,这样对调用者来说就是透明的,如下代码所示,但是注意哦,使用如下所示的方式会直接连系统新建的view的都交换了,所以慎重

[init]
1234567891011121314151617181920
@interface UIView (STSwizzle)- (instancetype) st_initWithFrame:(CGRect) frame;@end@implementation UIView (STSwizzle)+ (void) load {    method_exchangeImplementations(class_getInstanceMethod(self, @selector(initWithFrame:)), class_getInstanceMethod(self, @selector(st_initWithFrame:)));}- (instancetype) st_initWithFrame:(CGRect) frame {    NSLog(@"===before invoke initWithFrame==");    id instance = [self st_initWithFrame:frame];    NSLog(@"===after invoke initWithFrame==");    return instance;}@end

这里再次提醒一下,method_exchangeImplementations 方法可以多次被调用,如果你交换两次,就又交换回来了,你们可以自己试一下,如果要用的过程中,没有自信的话,可以增加变量来控制是否交换。
以上就完成了一种iOS的动态代理,类似于面向切面编程,其中用到了runtime的一些简单的知识,我们可以在原有东西执行之前/之后插入一些自定义的东西,大家可以自己做一做实验。Demo可以通过github 下载。
下面提供一个我这里的使用场景,场景是这样的,虽然系统的imageNamed可以根据当前设备自动查找@2x.png/.png图片,但是568h出现之后,有的地方我们需要使用568h的图片,这里我们就通过这种面向切面的方式,在系统的imageNamed:之前,去寻找合适的,存在的 imageName,然后调用系统的imageNamed。代码会在后续过程中放出,大家可以自己尝试实现。

0 0
原创粉丝点击