测试已测试 | ✗ |
语言语言 | Obj-CObjective C |
许可证 | MIT |
发布最后发布 | 2017年2月 |
由 ImageView 维护。
要运行示例项目,请克隆仓库,并首先从 Example 目录运行 pod install
命令。
DZViewControllerLifeCircleAction 可通过 CocoaPods 获取。要安装它,只需将以下行添加到 Podfile:
pod "DZViewControllerLifeCircleAction"
这篇系列文章是 2016 年工作总结的一部分,对这一年中遇到和解决的问题进行了梳理和总结。
在上一篇文章iOS 架构设计解耦的尝试之模块间通信中提到了如何维护全局 UI 堆栈。在写这篇文章时,发现其背后还有一个更有趣的问题:使用 AOP 对 VC 的业务逻辑进行切割。在 DZURLRoute 中使用到的全局 UI 堆栈就是基于这一思想构建的。这一部分的成果在库DZViewControllerLifeCircleAction中总结成了 Code(Talk is cheap. Show me the code)。而在iOS 架构设计系列之解耦的尝试之变异的 MVVM中,我们提到了通过 MVVM 进行解耦,而这篇文章我们又通过另一种方式 AOP 来尝试进行解耦。感觉这一年一直在进行解耦:)
首先说 AOP,其实在前面的文章或者库中已经多次提及。比如进行逻辑注入的库MRLogicInjection,用于相应区域扩展的DZExtendResponse、用于防止重复点击的DZDeneyRepeat、用于界面上红点提醒的MagicRemind。在过去的一年中,对 AOP 进行了一系列深入的实践。而这次要说的 VC 逻辑切割,其实也可以视为 AOP 的一个实践。顺便说一句,Objective-C 是一门神奇的语言,它提供的动态性让我们可以对其进行很多有趣的改造,将 OC 改造成一个更强大的工具。而对其进行 AOP 改造是我发现的一个非常有意思的事情。
在软件业,AOP 是 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热门话题,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范例。利用 AOP 可以将业务逻辑的各个部分进行隔离,从而降低业务逻辑各部分之间的耦合度,提高程序的可重用性,同时提高开发效率。
上面这段话摘自百度百科,对 AOP 做了一个非常好的解释。您可以点击链接查看具体内容。关于 AOP,我只简要说明一下我自己的理解,以做补充。
OC 本来是一款 OOP 语言,我们通过封装、继承、多态来组织类之间的静态结构,然后实现我们的业务逻辑。但有时候,严格遵循 OOP 的思想去设计继承结构,会导致非常深的继承关系。这将增加整个系统的理解复杂度。这并不是我们希望的。另外,我们讲究设计时应该满足开闭原则,对变化是开放的,对修改是封闭的。然而,当我们的类继承结构比较复杂时,就很难做到这一点。我们先来看一个比较常见的例子:
└── Object
└── biont
├── Animal
│ ├── cat
│ └── dog
└── plant
我们现在要构建一个描述生物的系统(简化版),第一版我们做出了类似于上面的类结构。我们在 Animal 类中实现了 cat 和 dog 的公有行为,在 cat 和 dog 中各自实现了他们独有的行为。这个时候突然发现我们多了一个 sparrow 物种。但此时我们在 Animal 中描述的是所有动物都有四条腿,而 sparrow 只有两条腿,这导致原有的类结构无法满足现有需求,需要修改。
└── Object
└── biont
├── Animal
│ ├── flying
│ │ └── sparrow
│ └── reptile
│ ├── cat
│ └── dog
└── plant
为了能够引入 sparrow,我们修改了 Animal 类,将四条腿的描述放到了 reptile 类中,并修改了 Cat 和 Dog 的继承关系。这需要对原有三个类进行较大的修改。
如果使用 AOP 的方法,我们会如何处理这个问题呢?进行切割和组合。
我们会将四条腿独立出来,将爬行切割出来,将两条腿切割出来,将飞行的特性切割出来 ... ... dog 就是四条腿爬行的动物,sparrow 就是两条腿会飞的动物。没有层次深的类继承结构,更多的是组合,而一个具体的类更像是一个容器,用来容纳不同的职责。当将不同的职责组合在一起时,就得到了我们需要的类。AOP 提供了一套完整的工具,指导我们如何进行切割和组合。这也是我认为 AOP 最大的魅力之一。
类似于上面提到的例子,我们在编写 ViewController 的业务逻辑时,也有可能造成非常深的继承结构。但实际上,我们发现许多业务逻辑是可独立提取的。例如:
有些事情我们通过类继承来实现,比如记录日志,找一个合适的类,在其中编写记录日志的逻辑。但当在继承树末端的 ViewController 中发现不需要记录日志时,那就麻烦了。必须进行大量的更改来适配需求。但是,如果这些业务逻辑像积木一样,需要时拿过来用,不需要时不管它,岂不是更好?需要记录日志的时候,拿一个记录日志的积木放进去,不需要的时候把记录日志的积木拿出来。
这就是AOP,面向切面编程。我们在ViewController上所选择进行逻辑编制的切面就是UIViewController的各种展示回调:
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
选择这四个函数作为切面是因为在实际编程过程中发现我们绝大多数的业务逻辑的起点都在这里面,还有一些在viewDidLoad中。不过按照语义来讲,viewDidLoad中应该是更多的对于VC中属性变量的初始化工作,而不是业务逻辑的处理。在DZViewControllerLifeCircleAction的设计中,我们更多的是关注到ViewController的展示周期内会做的一些事情。就像:
对应的我们在抽象出来的职责基类DZViewControllerLifeCircleBaseAction中提供了具体的编程接口:
/**
When a instance of UIViewController's view will appear , it will call this method. And post the instance of UIViewController
@param vc the instance of UIViewController that will appear
@param animated appearing is need an animation , this will be YES , otherwise NO.
*/
- (void) hostController:(UIViewController*)vc viewWillAppear:(BOOL)animated;
/**
When a instance of UIViewController's view did appeared. It will call this method, and post the instance of UIViewController which you can modify it.
@param vc the instance of UIViewController that did appeared
@param animated appearing is need an animation , this will be YES, otherwise NO.
*/
- (void) hostController:(UIViewController*)vc viewDidAppear:(BOOL)animated;
/**
When a instance of UIViewController will disappear, it will call this method, and post the instance of UIViewController which you can modify it.
@param vc the instance of UIViewController that will disappear
@param animated dispaaring is need an animation , this will be YES, otherwise NO.
*/
- (void) hostController:(UIViewController*)vc viewWillDisappear:(BOOL)animated;
/**
When a UIViewController did disappear, it will call this method ,and post the instance of UIViewController which you can modify it.
@param vc the instance of UIViewControll that did disppeared.
@param animated disappearing is need an animation, this will be YES, otherwise NO.
*/
- (void) hostController:(UIViewController*)vc viewDidDisappear:(BOOL)animated;
一个独立的职责可以集成基类创建一个子类,重载上述编程接口,进行逻辑编制。在展示周期内编写自己特定的逻辑。这里建议将这些逻辑尽可能地切割成粒度较小的逻辑单元。
在后续版本中也会考虑增加其他函数切入点的支持。
而所有的这些职责可以分为两类:
因而,在ViewController中设计职责容器时,也对应设计两个职责容器:
可以通过接口:
/**
This function will remove the target instance from the global cache . Global action will be call when every UIViewController appear. if you want put some logic into every instance of UIViewController, you can user it.
@param action the action that will be rmeove from global cache.
*/
FOUNDATION_EXTERN void DZVCRemoveGlobalAction(DZViewControllerLifeCircleBaseAction* action);
/**
This function will add an instance of DZViewControllerLifeCircleBaseAction into the global cache. Global action will be call when every UIViewController appear. if you want put some logic into every instance of UIViewController, you can user it.
@param action the action that will be insert into global cache
*/
FOUNDATION_EXTERN void DZVCRegisterGlobalAction(DZViewControllerLifeCircleBaseAction* action);
来增加或删除职责。
可以通过下述接口进行添加或删除职责:
@interface UIViewController (appearSwizzedBlock)
/**
add an instance of DZViewControllerLifeCircleBaseAction to the instance of UIViewController or it's subclass.
@param action the action that will be inserted in to the cache of UIViewController's instance.
*/
- (DZViewControllerLifeCircleBaseAction* )registerLifeCircleAction:(DZViewControllerLifeCircleBaseAction *)action;
/**
remove an instance of DZViewControllerLifeCircleBaseAction from the instance of UIViewController or it's subclass.
@param action the action that will be removed from cache.
*/
- (void) removeLifeCircleAction:(DZViewControllerLifeCircleBaseAction *)action;
@end
先拿我们刚才一直说的Log的例子来说,我们可以写一个专门打Log的Action:
@interface DZViewControllerLogLifeCircleAction : DZViewControllerLifeCircleBaseAction
@end
@implementation DZViewControllerLogLifeCircleAction
+ (void) load
{
DZVCRegisterGlobalAction([DZViewControllerLogLifeCircleAction new]);
}
- (void) hostController:(UIViewController *)vc viewDidDisappear:(BOOL)animated
{
[super hostController:vc viewDidDisappear:animated];
[TalkingData trackPageBegin:YHTrackViewControllerPageName(vc)];
}
- (void) hostController:(UIViewController *)vc viewDidAppear:(BOOL)animated
{
[super hostController:vc viewDidAppear:animated];
[TalkingData trackPageEnd:YHTrackViewControllerPageName(vc)];
}
@end
在该类load时将该Action注册到通用职责容器中,这样所有的ViewController都能够打Log了。如果某一个ViewController不需要打Log可以直接选择屏蔽掉该Action。
好了,这个才是最终要说的正题。扯了半天,其实就是为了说这个全局的展示的UIStack是怎么维护的。首先要说明的是,此处的UIStack所维护的内容的是正在展示的ViewController的堆栈关系,而不是keywindow上ViewController的叠加关系。
当一个ViewController展示时他就入栈,当一个ViewController不在展示时就出栈。
因而在该UIStack中的内容是当前整个APP正在展示的ViewController的堆栈。而他的实现原理就是继承DZViewControllerLifeCircleBaseAction并在viewAppear时入栈,在viewDisappear时出栈。
@implementation DZUIStackLifeCircleAction
+ (void) load
{
DZUIShareStack = [DZUIStackLifeCircleAction new];
DZVCRegisterGlobalAction(DZUIShareStack);
}
- (void) hostController:(UIViewController *)vc viewDidAppear:(BOOL)animated
{
[super hostController:vc viewDidAppear:animated];
//入栈
if (vc) {
[_uiStack addPointer:(void*)vc];
}
}
//出栈
- (void) hostController:(UIViewController *)vc viewDidDisappear:(BOOL)animated
{
[super hostController:vc viewDidDisappear:animated];
NSArray* allObjects = [_uiStack allObjects];
for (int i = (int)allObjects.count-1; i >= 0; i--) {
id object = allObjects[i];
if (vc == object) {
[_uiStack replacePointerAtIndex:i withPointer:NULL];
}
}
[_uiStack compact];
}
....
@end
同样也注册为一个通用职责。上面这两个例子下来,就已经在ViewController中加入了两个通用职责了。而这些职责之间都是隔离的,是代码隔离的那种!!!
在ViewController编程时,我们经常会写一些类似于_firstAppear这样的BOOL类型的变量,来标记这个VC是第一次被展示,然后做一些特定的动作。其实这个就是在VC所有的展示周期内只做一次的操作,针对此需求我们可以写一个这样的Action:
/**
The action block to handle ViewController appearing firstly.
@param vc The UIViewController tha appear
@param animated It will aminated paramter from the origin SEL paramter.
*/
typedef void (^DZViewControllerOnceActionWhenAppear)(UIViewController* vc, BOOL animated);
/**
when a ViewController appear firstly , it will do something . This class is design for this situation
*/
@interface DZVCOnceLifeCircleAction : DZViewControllerLifeCircleBaseAction
/**
The action block to handle ViewController appearing firstly.
*/
@property (nonatomic, strong) DZViewControllerOnceActionWhenAppear actionBlock;
/**
Factory method to reduce an instance of DZViewControllerOnceActionWhenAppear
@param block The handler to cover UIViewController appearing firstly
@return an instance of DZViewControllerOnceActionWhenAppear
*/
+ (instancetype) actionWithOnceBlock:(DZViewControllerOnceActionWhenAppear)block;
/**
a once action is an class that handle some logic once when one instance of UIViewController appear. It need a block to exe the function.
@param the logic function to exe
@return an instance of DZVCOnceLifeCircleAction
*/
- (instancetype) initWithBlock:(DZViewControllerOnceActionWhenAppear)block;
@end
该Action默认包含在DZViewControllerLifeAction库中了。当有VC需要这种责任时直接注入即可,例如:
[tableVC registerLifeCircleAction:[DZVCOnceLifeCircleAction actionWithOnceBlock:^(UIViewController *vc, BOOL animated) {
[[DZContactMonitor userMonitor] asyncLoadSystemContacts];
}]];
上面我们举了通用职责和专用职责的例子,都还算是比较简单的例子。其实,就是希望将职责拆解成更小的单元。然后组合使用。在我的APP中还有更加复杂的关于应用ViewController的AOP的例子。我把一个完整的逻辑模块,比如弹幕功能作为逻辑单元,基于DZViewControllerLifeAction来编写,当某个界面需要弹幕时,就将其作为专用职责进行逻辑注入。这样一来,发现你完全可以复用一整块原先可能完全不能复用的逻辑。在解耦和复用这条路上,这种方式算是目前我做的比较疯狂的事情了。非常有意思。
yishuiliunian, [email protected]
DZViewControllerLifeCircleAction 是基于MIT 许可证可用的。查看LICENSE文件获取更多信息。