我们开发一个项目都是慢慢完善的,设想一个很坏的情况:你所有的controller都没有基类,都是互相独立的。现在出现一个新的需求,当用户点击导航栏的返回按钮时,需要询问用户是否退出。那么这种情况下我们只能在当前controller自定义一个返回按钮,给它添加点击事件,最后我们在controller增加了几行代码,实现了此功能,如果其它controller也有此需求,我们又如法炮制。往后又有新的需求,需要改变提示框的样式,我们又需要一个个去修改,这样如此往复,代码便极其难维护,且开发效率低下。
所以导航控制器返回事件需要我们统一维护起来,像上篇一样我们可以自定义NavigationController,当push方法被调用时,我们统一设置返回按钮。但我们的项目已经开发的很庞大了,导航控制器我们使用的都是UINavigationController,如果我们此时自定义NavigationController,再去一一替换,也是一件极其耗时的事。此时我们就在想能不能再不影响整体项目的结构下,实现此功能。我们只想在push时去执行我们一段额外的代码片段,去设置返回按钮,此时AOP就派上用场了。
AOP即面向切面编程,这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。众所周知,iOS允许我们在runtime时,用自定义的方法替换系统原有的方法,替换后我们可以调用一遍原先的方法,并且也可以写入一段代码片段,这样我们就可以把一段代码切入到这个指定的方法中。
说了一大段,接下来我们看看怎么去具体实现。
首先我们创建一个NSObject 的Category,给他扩展一个静态方法: +(void)aspect_hookSelector:(SEL)selector block:(Block) block;
selector 指我们切入点,就是我们需要替换掉的方法。
block 当selector 被调用时,利用block回调通知,执行嵌入block中的代码片段。
而在 aspect_hookSelector 方法中将 selector 替换成我们自定义的方法就可以了
Method hookSelector = class_getInstanceMethod(self, selector);
NSString *selectorName = @"customizedSelector:";
SEL s = NSSelectorFromString(selectorName);
Method customizedSelector = class_getInstanceMethod(self, s);
method_exchangeImplementations(hookSelector, customizedSelector);
当自定义的方法被调起,回调block,然后执行block中的代码片段:
-(void)customizedSelector:(id)sender{
[self customizedSelector:sender]; // 执行系统原有的方法,应为已经被我们替换,所以如此调用
if(block)
block(self);
}
PS:block 利用单例存储,具体实现细节见Demo,博客中只贴主要代码。
回到我们的需求中,我们需要NavigationController push 时,去执行我们设置返回按钮的代码片段,那 pushViewController:animated: 就是我们的切入点:
[UINavigationController aspect_hookSelector:@selector(pushViewController:animated:) block:^(UIViewController *viewController) {
[self setBackBarButtonItem:viewController];
}];
我们可以把上段代码放到AppDelegate 中,程序一启动,就去执行。这样就会应用到项目中所有的UINavigationController, 而你不必再去修改项目中的其他文件,不必去创建父类,子类。你可以安安心心的在你的controller中专注的去写业务逻辑,这样就简单的做到了业务逻辑与业务功能的分离,后面也很方便维护。
好了,快结束了,从两篇文章中我们可以看出,OPP它精巧,封装了一个个实用的对象,API, 好像绣花针一般。而我们的AOP就可以比喻成一个砍柴斧,它只要找准一个切入点,就会影响很多东西。他们相互补充,希望大家可以合理使用它们,开发出优秀的项目。
最后贴一点福利,上面我们自己利用runtime机制简单的实现了AOP的开发,iOS端其实已经有一个非常强大的第三方库:Aspects ,从名字上就可以看出它是为AOP准备的,它非常小巧,只有一个.h 和.m 文件。
Aspects 允许你在某个类或者某个实例中已经存在的方法添加一段代码,并且你可以决定这段代码的执行位置,在方法中原有逻辑之前执行,还是之后执行,或者直接替代掉此方法。大家感兴趣,可以去学习一下这个非常容易上手的第三方库。
Aspects 使用也非常方便,或许你会有点眼熟,因为上面的自定义的那个方法是模仿它写的。
NSError *error;
[UINavigationController aspect_hookSelector:@selector(pushViewController:animated:) withOptions:AspectPositionAfter
usingBlock:^(id <AspectInfo> aspectInfo){
UINavigationController *navigationController = aspectInfo.instance;
if(navigationController.viewControllers.count>0){
UIViewController *viewController = navigationController.viewControllers[navigationController.viewControllers.count-1];
[self setBackBarButtonItem:viewController];
}
} error:&error];
到这里就结束了,具体代码实现细节,见Demo:
https://github.com/pzhtpf/CustomizedNavigationController