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開發知識 :掃描下方二維碼關注

相關文章
相關標籤/搜索