AOP
OC本來是個OOP的語言鳞溉,我們通過封裝瘾带、繼承、多態(tài)來組織類之間的靜態(tài)結(jié)構(gòu)熟菲,然后去實現(xiàn)我們的業(yè)務(wù)邏輯看政。只不過有些時候,嚴(yán)格遵循OOP的思想去設(shè)計繼承結(jié)構(gòu)抄罕,會產(chǎn)生非常深的繼承關(guān)系允蚣。這勢必要增加整個系統(tǒng)的理解復(fù)雜度。而這并不是我們希望的呆贿。另外一點嚷兔,我們講究設(shè)計的時候能夠滿足開閉原則森渐,對變化是開放的,對于修改是封閉的冒晰。然而當(dāng)我們的類繼承結(jié)構(gòu)比較復(fù)雜的時候同衣,就很難做到這一點。我們先來看一個比較Common的例子:
└── Object
└── biont
├── Animal
│ ├── cat
│ └── dog
└── plant
我們現(xiàn)在要構(gòu)建一個用于描述生物的系統(tǒng)(精簡版)壶运,第一版我們做出了類似于上面的類結(jié)構(gòu)耐齐。我們在Animal類中寫了cat和dog的公有行為,在cat和dog中各自描述了他們獨有的行為蒋情。這個時候突然發(fā)現(xiàn)我們多了一個sparrow物種埠况。但是呢我們在Animal中描述的是動物都有四條腿,而sparrow只有兩條腿恕出,于是原有的類結(jié)構(gòu)就不能滿足現(xiàn)在的需求了询枚,就得改啊。
└── Object
└── biont
├── Animal
│ ├── flying
│ │ └── sparrow
│ └── reptile
│ ├── cat
│ └── dog
└── plant
為了能夠引入sparrow我們修改了Animal類浙巫,將四條腿的描述放到了reptile類中金蜀,并修改了Cat和Dog的繼承關(guān)系。修改的變動量還是不小的的畴。引入了兩個新類渊抄,并對原有三個進(jìn)行比較大的改動。
而如果用AOP的話我們會怎么處理這個事情呢丧裁?切割和組合护桦。
我們會將四條腿獨立出來,爬行切割出來煎娇,兩條腿切割出來二庵,會飛切割出來 。缓呛。催享。然后dog就是四條腿爬行的動物。sparrow就是兩條腿會飛的動物哟绊。沒有了層次深的類繼承結(jié)構(gòu)因妙。更多的是組合,而一個具體的類更像是一個容器票髓,用來容納不同的職責(zé)攀涵。當(dāng)把這些不同的職責(zé)組合在一起的時候就得到了我們需要的類。AOP則提供一整套的瑞士軍刀洽沟,指導(dǎo)你如何進(jìn)行切割以故,并如何進(jìn)行組合。這也是我認(rèn)為AOP的最大魅力裆操。
DZViewControllerLifeCircleAction 對VC進(jìn)行邏輯切割和組合
github地址:https://github.com/yishuiliunian/DZViewControllerLifeCircleAction
類似于上面我們提到的例子怒详,我們在寫ViewController的業(yè)務(wù)邏輯的時候鳄乏,也有可能造成非常深的繼承結(jié)構(gòu)。而我們其實發(fā)現(xiàn)在眾多的業(yè)務(wù)邏輯中棘利,有些東西是可以單獨抽離出來的。比如:
我們會在頁面第一次viewWillAppear的時候刷新一次數(shù)據(jù)朽缴,這個在TableViewController會這樣善玫,在CollectionViewController的時候也會這樣。
我們會在生命周期打Log密强,對用戶的使用路徑進(jìn)行上報茅郎。
….
有些事情我們通過類繼承來做了,比如打Log或渤,找一個根類系冗,在里面把打Log的邏輯寫了。但是當(dāng)發(fā)現(xiàn)在繼承樹的末端有一個ViewController不需要打Log的時候就尷尬了薪鹦。得大費周折的去改類結(jié)構(gòu)掌敬,來適配這個需求。但是池磁,如果這些業(yè)務(wù)邏輯像是積木一樣奔害,需要的時候拿過來用,不需要的時候不管他地熄,多好华临。這樣需要打Log的時候,拿過來一個打Log的積木堆進(jìn)去端考,不需要的時候把打Log的積木拿走雅潭。
職責(zé)編程界面(API)
而這就是AOP,面向切面編程却特。我們在ViewController上所選擇進(jìn)行邏輯編制的切面就是UIViewController的各種展示回調(diào):
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
選擇這四個函數(shù)做為切面是因為在實際的編程過程中發(fā)現(xiàn)我們絕大多數(shù)的業(yè)務(wù)邏輯的起點都在這里面扶供,還有一些在viewDidLoad里面。不過按照語義來講核偿,viewDidLoad中應(yīng)該是更多的對于VC中屬性變量的初始化工作诚欠,而不是業(yè)務(wù)邏輯的處理。在DZViewControllerLifeCircleAction的設(shè)計的時候漾岳,我們更多的是關(guān)注到ViewController的展示周期內(nèi)會做的一些事情轰绵。就像:
1.在頁面第一次顯示的時候進(jìn)行數(shù)據(jù)加載
2.每一次展示的時候增加xxx的觀察者通知,在不展示的時候移除
3.在頁面第一次展示的時候執(zhí)行特殊的動作
4.構(gòu)建特殊的頁面邏輯
5.尼荆。左腔。。捅儒。液样。振亮。
對應(yīng)的我們在抽象出來的職責(zé)基類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;
一個獨立的職責(zé)可以繼承基類創(chuàng)建一個子類,重載上述編程接口鞭莽,進(jìn)行邏輯編制坊秸。在展示周期內(nèi)去寫自己都有的邏輯。這里建議將這些邏輯盡可能的切割成粒度較小的邏輯單元澎怒。在自己的時間中發(fā)現(xiàn)褒搔,相對較小的粒度可以獲得更高的業(yè)務(wù)邏輯隔離和解耦效果。
職責(zé)注入與刪除編程界面
而所有的這些職責(zé)喷面,可以分成兩類:
1.通用職責(zé)星瘾,表現(xiàn)為所有的UIViewController都會有的職責(zé),比如日志Log惧辈。
2.專用職責(zé)琳状,比如一個UITableViewController,需要在展示時才注冊xxx通知盒齿。
因而念逞,在ViewController中設(shè)計職責(zé)容器的時候,也對應(yīng)的設(shè)計了兩個職責(zé)容器:
DZViewControllerGlobalActions()用來承載通用職責(zé)
可以通過接口:
/**
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);
來增加或者刪除職責(zé)边翁。
專用職責(zé)容器
可以通過下述接口進(jìn)行添加或者刪除職責(zé):
@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的時候?qū)⒃揂ction注冊到通用職責(zé)容器中,這樣所有的ViewController都能夠打Log了倒彰。如果某一個ViewController不需要打Log可以直接選擇屏蔽掉該Action审洞。
UIStack
好了,這個才是最終要說的正題待讳。扯了半天芒澜,其實就是為了說這個全局的展示的UIStack是怎么維護(hù)的。首先要說明的是创淡,此處的UIStack所維護(hù)的內(nèi)容的是正在展示的ViewController的堆棧關(guān)系痴晦,而不是keywindow上ViewController的疊加關(guān)系。
當(dāng)一個ViewController展示的時候他就入棧琳彩,當(dāng)一個ViewController不在展示的時候就出棧誊酌。
因而在該UIStack中的內(nèi)容是當(dāng)前整個APP正在展示的ViewController的堆棧。而他的實現(xiàn)原理就是繼承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
同樣也注冊為一個通用職責(zé)。上面這兩個例子下來瘟仿,就已經(jīng)在ViewController中加入了兩個通用職責(zé)了箱锐。而這些職責(zé)之間都是隔離的,是代碼隔離的那種@徒稀>灾埂浩聋!
執(zhí)行一次的Action, 專用職責(zé)的例子
在ViewController編程的時候臊恋,我們經(jīng)常會寫一些類似于_firstAppear這樣的BOOL類型的變量衣洁,來標(biāo)記這個VC是第一次被展示,然后做一些特定的動作抖仅。其實這個就是在VC所有的展示周期內(nèi)只做一次的操作闸与,真對這個需求我們可以寫一個這樣的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默認(rèn)包含在DZViewControllerLifeAction庫中了。當(dāng)有VC需要這種指責(zé)的時候直接注入就行了岸售,例如:
[tableVC registerLifeCircleAction:[DZVCOnceLifeCircleAction actionWithOnceBlock:^(UIViewController *vc, BOOL animated) {
[[DZContactMonitor userMonitor] asyncLoadSystemContacts];
}]];
其他
上面我們舉了通用職責(zé)和專用職責(zé)的例子,都還算是比較簡單的例子厂画。其實凸丸,就是希望把職責(zé)拆解成粒度更小的單元。然后組合使用袱院。而在我的APP中還有更加復(fù)雜的關(guān)于應(yīng)用ViewController的AOP的例子屎慢。我把一個整個邏輯模塊,比如彈幕功能做為了一個邏輯單元忽洛,基于DZViewControllerLifeAction來寫腻惠,當(dāng)某個界面需要彈幕的時候,就當(dāng)做專用職責(zé)進(jìn)行邏輯注入欲虚。而這樣一來集灌,發(fā)現(xiàn)你完全可以復(fù)用一整塊原先可能完全不能復(fù)用的邏輯。在解耦和復(fù)用這條路上复哆,這種方式算是目前我做的比較瘋狂的事情了欣喧。非常有意思。
BY:yishuiliunian