聊聊 AOP 模式與 ObjC 對它的實現
說到 AOP 可能有些經驗的小伙伴都有所了解,今天我們來聊聊關于它的內容,以及 ObjC 中如何實現它。
AOP 簡述
在聊具體實現之前,我們先從設計層面介紹一下 AOP 的由來,以及它解決了什么問題。 了解這個模式后,其實它可以用到任何的語言實現中。 AOP 的全稱叫做 Aspect-oriented programming ,維基百科上面有對它的完整解釋:
按照字面翻譯過來,它叫做面向方面編程。是不是聽起來怪怪的,不好理解。 下面我們就用最簡單的語言來描述一下。 所謂 AOP 其實就是給你的程序提供一個可拆卸的組件化能力。 這么說可能還不夠直觀,咱們直接上代碼。
比如你的 APP 需要用到事件統計功能,這個場景應該大多數伙伴都會遇到過。 無論你是用 UMeng, Google Analytics, 還是其他的統計平臺等等, 你應該都會寫過類似的代碼:
- (void)viewDidLoad {
[super viewDidLoad];
[Logger log:@"View Did Load"];
UILabel *label = [[UILabel alloc] init];
[self.view addSubview:label];
}
上面這段代碼會在試圖控制器開始加載的時候,用 Logger 類記錄一個統計事件。 這些統計相關的代碼是我們為了完成我們記錄 APP 運行數據這個邏輯的。 其實 viewDidLoad 方法本身的邏輯并不是為了完成統計,而是進行一些初始化操作, 比如這里初始化了 UILabel。
這就導致了一個設計上的瑕疵, 數據統計的代碼和我們實際的業務邏輯代碼混雜在一起了。 加入過了一段時間后, 你再回過頭來要改業務邏輯代碼的時候,你看到 Logger 的統計代碼混雜在一起,就必須要花點時間將他們的含義區分出來。
隨著業務邏輯代碼不斷增多,類似的混雜也會越來越多,這樣的耦合勢必會增加維護的成本。正式因為這個原因,所以誕生了 AOP 模式。 AOP 其實就是在不影響程序整體功能的情況下,將 Logger 這樣的邏輯,從主業務邏輯中抽離出來的能力。
簡單來說, 有了 AOP 之后, 我們的業務邏輯代碼就變成了這樣:
- (void)viewDidLoad {
[super viewDidLoad];
UILabel *label = [[UILabel alloc] init];
[self.view addSubview:label];
}
這里不再會出現 Logger 的統計邏輯的代碼,但是統計功能依然是生效的。 當然,不出現在主業務代碼中,不代表統計代碼就消失了。 而是用 AOP 模式 hook 到別的地方去了。
ObjC 對 AOP 的實現
對 AOP 做了一個基本的介紹后,咱們就來看看如何實現它吧, 這里只舉了 ObjC 的例子,但 AOP 是一個模式,理論上大多數語言都可以實現這個模式。
ObjC 中實現 AOP 最直接的方法就是使用 runtime 中的 Method Swizzling。
還回到我們之前的 viewDidLoad 方法中,前面我們提到使用 AOP 后, viewDidLoad 方法中就會只包含業務邏輯代碼,而和主業務不相干的統計代碼就被剝離出去了。 具體是怎么做到的呢,我們看下面的代碼:
#import <objc/runtime.h>
#import "ViewController.h"
#import "Logger.h"
@interfaceAOPHelper:NSObject
+ (void) setup;
@end
@implementationAOPHelper
IMP originalViewDidLoadIMP;
void replacedViewDidLoad() {
originalViewDidLoadIMP();
[Logger log:@"View Did Load"];
}
+ (void)setup {
originalViewDidLoadIMP = class_getMethodImplementation([ViewController class], @selector(viewDidLoad));
Method originalViewDidLoad = class_getInstanceMethod([ViewController class], @selector(viewDidLoad));
method_setImplementation(originalViewDidLoad, (IMP) replacedViewDidLoad);
}
@end
AOPHelper 所做的工作就是 hook 我們主業務的邏輯, 首先通過 class_getMethodImplementation 得到 viewDidLoad 原始的實現, 也就是包含業務代碼的那個方法實現。 然后通過 class_getInstanceMethod 和 method_setImplementation 兩個方法用我們自己的實現 replacedViewDidLoad 替換掉原有的 viewDidLoad 實現。
再來看看我們自己的 replacedViewDidLoad:
void replacedViewDidLoad() {
originalViewDidLoadIMP();
[Logger log:@"View Did Load"];
}
這里面加入了 Logger 類的統計邏輯, 并且也通過 originalViewDidLoadIMP 調用了原有的業務邏輯實現。 originalViewDidLoadIMP 是通過前面 class_getMethodImplementation 方法取得的。
概括的來說,就是我們先將業務邏輯代碼和統計代碼分成兩個組件, 然后在 AOPHelper 中將他們組合起來。 這樣兩個邏輯在代碼層面各司其職,互不影響。 而在運行時,通過 AOP 的 hook 機制又將他們組合了起來。
額外的收益
使用了 AOP 之后,還會有一些額外的好處。 比如,如果你在哪一天想換一個統計平臺, 那么你不需要到處改代碼了, 只需要把統計層面的代碼修改一下就可以。 再比如,你想打一個不帶統計能力的安裝包,只需要將 hook 的部分去掉,就自動去掉統計邏輯了。 當想用的時候,再把它裝回來即可。 這個感覺就像是樂高積木一樣,通過 AOP 可以把某些能力變成一個模塊,即插即用。
剛才說的不帶統計安裝包的場景其實在我們的真是環境中還是存在的,比如一個開發團隊在初期測試自己的 APP 功能的時候,就不希望測試時候的會話計入統計數據中。
當然,AOP 帶來收益的同時,也會有一些損失,比如在寫代碼的時候就不那么直觀了。 這就取決于你對項目整體設計的權衡了,是健壯性優先,還是開發便捷性優先。根據不同的個人風格,不同的項目規模各自權衡。
結語
AOP 作為一種設計模式,他能為需要這種設計的項目解決實際中的問題。 你的項目中是否也要考慮使用 AOP 呢,我的建議是對于稍大些并且后期有維護價值的項目就值得使用。
Aspects, 可以不需要繁瑣的手工調用 Method Swizzling。 后面我們會繼續介紹 Aspects 這個庫。
如果你覺得這篇文章有幫助,還可以關注微信公眾號 swift-cafe,會有更多我的原創內容分享給你~
來自:http://www.swiftcafe.io/2017/01/03/objc-aop/