程式設計思想 面向切面程式設計

Simba_LX發表於2018-07-31

####1.什麼是面向切面程式設計 AOP(Aspect-Oriented Programming),面向切面程式設計,看著是跟OOP(物件導向程式設計)挺相近的,但實際上又有什麼區別呢?OOP具有封裝,繼承,多型等東西來定義從上到下這種層次關係,但要想實現從左到右的關係的話就開始有點水土不服了,例如使用者的許可權控制,操作日誌等,這些與我們要實現的核心功能不大有關係的東西散佈在我們程式碼的周邊,顯示十分不好看。於是我們引入了AOP的模式。

072234250927101.jpg
我們通常在實現一個頁面邏輯的時候,通常伴隨著操作日誌,安全監測,事務處理等幾個邏輯,在實現邏輯的時候都要寫一大堆這種程式碼。而AOP就是將這些與主體業務無關,但又有為業務提供服務的邏輯程式碼封裝起來,降低模組之間的耦合度。如圖所示中的圓柱體好比如我們的業務流程,aop代表的是那個橫向的操作,俗稱切面程式設計。或許上面的這些理論有點頭疼,對於AOP我的大體理解是:將那些與業務核心不大相關的雜七雜八的東西獨立開,每次實現了業務核心之前或之後,呼叫一下對應的方法。

注意:AOP不是一種技術,實際上是程式設計思想。凡是符合AOP思想的技術,都可以看成是AOP的實現

####2.AOP在iOS中的應用 在 Objective-C 的世界裡,有很多方式可以實現 AOP ,Method Swizzling 就是其中之一。而且幸運的是,目前已經有一些第三方庫可以讓你不需要了解 Runtime ,就能直接開始使用 AOP 。

Aspects 就是一個不錯的 AOP 庫,封裝了 Runtime , Method Swizzling 這些黑色技巧,只提供兩個簡單的API:

+ (id<aspecttoken>)aspect_hookSelector:(SEL)selector
                         withOptions:(AspectOptions)options
                      usingBlock:(id)block
                           error:(NSError **)error;- (id<aspecttoken>)aspect_hookSelector:(SEL)selector
                     withOptions:(AspectOptions)options
                      usingBlock:(id)block
                           error:(NSError **)error;</aspecttoken></aspecttoken>
複製程式碼

使用 Aspects 提供的 API,我們之前的例子會進化成這個樣子:

@implementation UIViewController (Logging)+ (void)load
{
   [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                             withOptions:AspectPositionAfter
                              usingBlock:^(id<aspectinfo> aspectInfo) {        NSString *className = NSStringFromClass([[aspectInfo instance] class]);
       [Logging logWithEventName:className];
                              } error:NULL];
}</aspectinfo>
複製程式碼

你可以用同樣的方式在任何你感興趣的方法裡新增自定義程式碼,比如 IBAction 的方法裡。更好的方式,你提供一個 Logging 的配置檔案作為唯一處理事件記錄的地方:

@implementation AppDelegate (Logging)+ (void)setupLogging
{    NSDictionary *config = @{        @"MainViewController": @{
           GLLoggingPageImpression: @"page imp - main page",
           GLLoggingTrackedEvents: @[
               @{
                   GLLoggingEventName: @"button one clicked",
                   GLLoggingEventSelectorName: @"buttonOneClicked:",
                   GLLoggingEventHandlerBlock: ^(id<aspectinfo> aspectInfo) {
                       [Logging logWithEventName:@"button one clicked"];
                   },
               },
               @{
                   GLLoggingEventName: @"button two clicked",
                   GLLoggingEventSelectorName: @"buttonTwoClicked:",
                   GLLoggingEventHandlerBlock: ^(id<aspectinfo> aspectInfo) {
                       [Logging logWithEventName:@"button two clicked"];
                   },
               },
          ],
       },        @"DetailViewController": @{
           GLLoggingPageImpression: @"page imp - detail page",
       }
   };
   [AppDelegate setupWithConfiguration:config];
}
+ (void)setupWithConfiguration:(NSDictionary *)configs
{    // Hook Page Impression
   [UIViewController aspect_hookSelector:@selector(viewDidAppear:)
                             withOptions:AspectPositionAfter
                              usingBlock:^(id<aspectinfo> aspectInfo) {                                       NSString *className = NSStringFromClass([[aspectInfo instance] class]);
                                   [Logging logWithEventName:className];
                              } error:NULL];    // Hook Events
   for (NSString *className in configs) {
       Class clazz = NSClassFromString(className);        NSDictionary *config = configs[className];        if (config[GLLoggingTrackedEvents]) {            for (NSDictionary *event in config[GLLoggingTrackedEvents]) {
               SEL selekor = NSSelectorFromString(event[GLLoggingEventSelectorName]);
               AspectHandlerBlock block = event[GLLoggingEventHandlerBlock];
               [clazz aspect_hookSelector:selekor
                              withOptions:AspectPositionAfter
                               usingBlock:^(id<aspectinfo> aspectInfo) {
                                   block(aspectInfo);
                               } error:NULL];
           }
       }
   }
}</aspectinfo></aspectinfo></aspectinfo></aspectinfo>
複製程式碼

然後在 -application:didFinishLaunchingWithOptions:裡呼叫 setupLogging:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    // Override point for customization after application launch.
   [self setupLogging];    return YES;
}
複製程式碼

最後的話 利用 objective-C Runtime 特性和 Aspect Oriented Programming ,我們可以把瑣碎事務的邏輯從主邏輯中分離出來,作為單獨的模組。它是對物件導向程式設計模式的一個補充。Logging 是個經典的應用,這裡做個拋磚引玉,發揮想象力,可以做出其他有趣的應用。 使用 Aspects 完整的例子可以從這裡獲得:AspectsDemo

相關文章