談一談攔截導航控制器返回事件(下)——AOP

前文鏈接:談一談攔截導航控制器返回事件(上)——OPP

我們開發一個項目都是慢慢完善的,設想一個很壞的情況:你所有的controller都沒有基類,都是互相獨立的。現在出現一個新的需求,當用戶點擊導航欄的返回按鈕時,需要詢問用戶是否退出。那麼這種情況下我們只能在當前controller自定義一個返回按鈕,給它添加點擊事件,最後我們在controller增加了幾行代碼,實現了此功能,如果其它controller也有此需求,我們又如法炮製。往後又有新的需求,需要改變提示框的樣式,我們又需要一個個去修改,這樣如此往復,代碼便極其難維護,且開發效率低下。
所以導航控制器返回事件需要我們統一維護起來,像上篇一樣我們可以自定義NavigationController,當push方法被調用時,我們統一設置返回按鈕。但我們的項目已經開發的很龐大了,導航控制器我們使用的都是UINavigationController,如果我們此時自定義NavigationController,再去一一替換,也是一件極其耗時的事。此時我們就在想能不能再不影響整體項目的結構下,實現此功能。我們只想在push時去執行我們一段額外的代碼片段,去設置返回按鈕,此時AOP就派上用場了。
AOP即面向切面編程,這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。衆所周知,iOS允許我們在runtime時,用自定義的方法替換系統原有的方法,替換後我們可以調用一遍原先的方法,並且也可以寫入一段代碼片段,這樣我們就可以把一段代碼切入到這個指定的方法中。
說了一大段,接下來我們看看怎麼去具體實現。
首先我們創建一個NSObject 的Category,給他擴展一個靜態方法:
+(void)aspect_hookSelector:(SEL)selector block:(Block) block;

selector 指我們切入點,就是我們需要替換掉的方法。
block 當selector 被調用時,利用block回調通知,執行嵌入block中的代碼片段。

而在 aspect_hookSelector 方法中將 selector 替換成我們自定義的方法就可以了

Method hookSelector = class_getInstanceMethod(self, selector);

NSString *selectorName = @"customizedSelector:";
SEL s = NSSelectorFromString(selectorName);

Method customizedSelector = class_getInstanceMethod(self, s);

method_exchangeImplementations(hookSelector, customizedSelector);

當自定義的方法被調起,回調block,然後執行block中的代碼片段:

-(void)customizedSelector:(id)sender{
    [self customizedSelector:sender];   // 執行系統原有的方法,應爲已經被我們替換,所以如此調用

    if(block)
        block(self);
}

PS:block 利用單例存儲,具體實現細節見Demo,博客中只貼主要代碼。

回到我們的需求中,我們需要NavigationController push 時,去執行我們設置返回按鈕的代碼片段,那 pushViewController:animated: 就是我們的切入點:

[UINavigationController aspect_hookSelector:@selector(pushViewController:animated:) block:^(UIViewController *viewController) {
       [self setBackBarButtonItem:viewController];
 }];

我們可以把上段代碼放到AppDelegate 中,程序一啓動,就去執行。這樣就會應用到項目中所有的UINavigationController, 而你不必再去修改項目中的其他文件,不必去創建父類,子類。你可以安安心心的在你的controller中專注的去寫業務邏輯,這樣就簡單的做到了業務邏輯與業務功能的分離,後面也很方便維護。

好了,快結束了,從兩篇文章中我們可以看出,OPP它精巧,封裝了一個個實用的對象,API, 好像繡花針一般。而我們的AOP就可以比喻成一個砍柴斧,它只要找準一個切入點,就會影響很多東西。他們相互補充,希望大家可以合理使用它們,開發出優秀的項目。

最後貼一點福利,上面我們自己利用runtime機制簡單的實現了AOP的開發,iOS端其實已經有一個非常強大的第三方庫:Aspects ,從名字上就可以看出它是爲AOP準備的,它非常小巧,只有一個.h 和.m 文件。

Aspects 允許你在某個類或者某個實例中已經存在的方法添加一段代碼,並且你可以決定這段代碼的執行位置,在方法中原有邏輯之前執行,還是之後執行,或者直接替代掉此方法。大家感興趣,可以去學習一下這個非常容易上手的第三方庫。

Aspects 使用也非常方便,或許你會有點眼熟,因爲上面的自定義的那個方法是模仿它寫的。

NSError *error;
[UINavigationController aspect_hookSelector:@selector(pushViewController:animated:)                                   withOptions:AspectPositionAfter
usingBlock:^(id <AspectInfo> aspectInfo){
                                              UINavigationController *navigationController = aspectInfo.instance;
                                             if(navigationController.viewControllers.count>0){
                                                 UIViewController *viewController = navigationController.viewControllers[navigationController.viewControllers.count-1];
[self setBackBarButtonItem:viewController];
                                             }

                                        }                                           error:&error];

到這裏就結束了,具體代碼實現細節,見Demo:
https://github.com/pzhtpf/CustomizedNavigationController

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章