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

AOP

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

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


iOS架构设计解耦的尝试之VC逻辑AOP切割_第1张图片
7df22103ly1fbmp5nht8gj208x06q74q.jpg

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

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

github地址:https://github.com/yishuiliunian/DZViewControllerLifeCircleAction
类似于上面我们提到的例子,我们在写ViewController的业务逻辑的时候,也有可能造成非常深的继承结构。而我们其实发现在众多的业务逻辑中,有些东西是可以单独抽离出来的。比如:

我们会在页面第一次viewWillAppear的时候刷新一次数据,这个在TableViewController会这样,在CollectionViewController的时候也会这样。
我们会在生命周期打Log,对用户的使用路径进行上报。
….
有些事情我们通过类继承来做了,比如打Log,找一个根类,在里面把打Log的逻辑写了。但是当发现在继承树的末端有一个ViewController不需要打Log的时候就尴尬了。得大费周折的去改类结构,来适配这个需求。但是,如果这些业务逻辑像是积木一样,需要的时候拿过来用,不需要的时候不管他,多好。这样需要打Log的时候,拿过来一个打Log的积木堆进去,不需要的时候把打Log的积木拿走。

职责编程界面(API)

而这就是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

使用举例

先拿我们刚才一直再说的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来写,当某个界面需要弹幕的时候,就当做专用职责进行逻辑注入。而这样一来,发现你完全可以复用一整块原先可能完全不能复用的逻辑。在解耦和复用这条路上,这种方式算是目前我做的比较疯狂的事情了。非常有意思。

BY:yishuiliunian

你可能感兴趣的:(iOS架构设计解耦的尝试之VC逻辑AOP切割)