iOS架构设计解耦的尝试之VC逻辑AOP切割

该系列文章是2016年折腾的一个总结,对于这一年中思考和解决的一些问题作一些梳理和总结javascript

上一篇文章iOS架构设计解耦的尝试之模块间通讯中提到要说一下全局UI堆栈是怎么维护的。要写的时候发现,这个东西背后还有一个更有意思的东西:使用AOP对VC的业务逻辑进行切割。在DZURLRoute中所使用到的全局UI堆栈就是基于该思想构建出来的。这一部分的成果在库DZViewControllerLifeCircleAction中总结成了Code(Talk is cheap. Show me the code)。而咱们在
iOS架构设计系列之解耦的尝试之变异的MVVM
中提到了经过MVVM来进行解耦,而这篇文章咱们又经过另一种方式AOP来尝试进行解耦。感受这一年在疯狂的解耦:)。html

AOP

先从AOP提及,其实在以前的文章中或者开发的库中已经涉及到过不少次。好比对于Instance进行逻辑注入的库MRLogicInjection,基于MRLogicInjection的应用方案用于相应区域扩展的DZExtendResponse、用于放重复点击的DZDeneyRepeat、用于界面上红点提醒的MagicRemind。这一年对于AOP也算有了一个比较深刻的实践。而此次要说的VC逻辑切割,其实也算是AOP的一个实践。说句题外话,Objective-C是门神奇的语言,他提供的动态性,让咱们能够对其进行不少有意思的改造,把OC改形成一个更好用的工具。而对其进行AOP改造就是我发现的很是有意思的一个事情。java

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP能够对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度下降,提升程序的可重用性,同时提升了开发的效率。ios

上面这段文字摘自百度百科。对于AOP作了一个很是好的解释,点击连接能够进去看看具体的内容。关于AOP只简短的说一下我本身的理解,以做补充。git

OC原本是个OOP的语言,咱们经过封装、继承、多态来组织类之间的静态结构,而后去实现咱们的业务逻辑。只不过有些时候,严格遵循OOP的思想去设计继承结构,会产生很是深的继承关系。这势必要增长整个系统的理解复杂度。而这并非咱们但愿的。另一点,咱们讲究设计的时候可以知足开闭原则,对变化是开放的,对于修改是封闭的。然而当咱们的类继承结构比较复杂的时候,就很难作到这一点。咱们先来看一个比较Common的例子:github

└── 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的话咱们会怎么处理这个事情呢?切割和组合。app

咱们会将四条腿独立出来,爬行切割出来,两条腿切割出来,会飞切割出来
。。。而后dog就是四条腿爬行的动物。sparrow就是两条腿会飞的动物。没有了层次深的类继承结构。更多的是组合,而一个具体的类更像是一个容器,用来容纳不一样的职责。当把这些不一样的职责组合在一块儿的时候就获得了咱们须要的类。AOP则提供一整套的瑞士军刀,指导你如何进行切割,并如何进行组合。这也是我认为AOP的最大魅力。框架

DZViewControllerLifeCircleAction 对VC进行逻辑切割和组合

相似于上面咱们提到的例子,咱们在写ViewController的业务逻辑的时候,也有可能形成很是深的继承结构。而咱们其实发如今众多的业务逻辑中,有些东西是能够单独抽离出来的。好比:

  1. 咱们会在页面第一次viewWillAppear的时候刷新一次数据,这个在TableViewController会这样,在CollectionViewController的时候也会这样。
  2. 咱们会在生命周期打Log,对用户的使用路径进行上报。
  3. ....

有些事情咱们经过类集成来作了,好比打Log,找一个跟类,在里面把打Log的逻辑写了。可是当发如今继承树的末端有一个ViewController不须要打Log的时候就尴尬了。得大费周折的去改类结构,来适配这个需求。可是,若是这些业务逻辑像是积木同样,须要的时候拿过来用,不须要的时候无论他,多好。这样须要打Log的时候,拿过来一个打Log的积木堆进去,不须要的时候把打Log的积木拿走。

职责编程界面

而这就是AOP,面向切面编程。咱们在ViewController上所选择进行逻辑编制的切面就是UIViewController的各类展现回调:

- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated复制代码

选择这四个函数作为切面是由于在实际的编程过程当中发现咱们绝大多数的业务逻辑的起点都在这里面,还有一些在viewDidLoad里面。不过按照语义来说,viewDidLoad中应该是更多的对于VC中属性变量的初始化工做,而不是业务逻辑的处理。在DZViewControllerLifeCircleAction的设计的时候,咱们更多的是关注到ViewController的展现周期内会作的一些事情。就像:

  1. 一次展现的进行数据加载
  2. 展现的时候增长xxx的通知,在不展现的时候移除
  3. 在第一次展现的时候执行特殊的动做
  4. 构建特殊的页面逻辑
  5. 。。。。。。

对应的咱们在抽象出来的职责基类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;复制代码

一个独立的职责能够集成基类建立一个子类,重载上述编程接口,进行逻辑编制。在展现周期内去写本身都有的逻辑。这里建议将这些逻辑尽量的切割成粒度较小的逻辑单元。

在后续版本中也会考虑增长其余函数切入点的支持。

职责注入与删除编程界面

而全部的这些职责,能够分红两类:

  1. 通用职责,表现为全部的UIViewController都会有的职责,好比日志Log。
  2. 专用职责,好比一个UITableViewController,须要在展现时才注册xxx通知。

于是,在ViewController中设计职责容器的时候,也对应的设计了两个职责容器:

DZViewControllerGlobalActions()用来承载通用职责

能够经过接口:

/** 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复制代码

使用举例

LogAction

先拿咱们刚才一直再说的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是怎么维护的。首先要说明的是,此处的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中加入了两个通用职责了。而这些职责之间都是隔离的,是代码隔离的那种!!!

执行一次的Action, 专用职责的例子

在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来写,当某个界面须要弹幕的时候,就当作专用职责进行逻辑注入。而这样一来,发现你彻底能够复用一整块原先可能彻底不能复用的逻辑。在解耦和复用这条路上,这种方式算是目前我作的比较疯狂的事情了。很是有意思。

欢迎关注iOS开发公共帐号 iOS开发知识 :扫描下方二维码关注

相关文章
相关标签/搜索