iOS解耦方式之AOP面向切面編程

來源:互聯網
上載者:User
MVVM解耦Demo和部落格介紹 面向切面編程(AOP) Demo思路

這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。

面向切面編程(AOP是Aspect Oriented Program的首字母縮寫) ,我們知道,物件導向的特點是繼承、多態和封裝。而封裝就要求將功能分散到不同的對象中去,這在軟體設計中往往稱為職責分配。實際上也就是說,讓不同的類設計不同的方法。這樣代碼就分散到一個個的類中去了。這樣做的好處是降低了代碼的複雜程度,使類可重用。
但是人們也發現,在分散代碼的同時,也增加了代碼的重複性。什麼意思呢。比如說,我們在兩個類中,可能都需要在每個方法中做日誌。按物件導向的設計方法,我們就必須在兩個類的方法中都加入日誌的內容。也許他們是完全相同的,但就是因為物件導向的設計讓類與類之間無法連絡,而不能將這些重複的代碼統一起來。
也許有人會說,那好辦啊,我們可以將這段代碼寫在一個獨立的類獨立的方法裡,然後再在這兩個類中調用。但是,這樣一來,這兩個類跟我們上面提到的獨立的類就有耦合了,它的改變會影響這兩個類。那麼,有沒有什麼辦法,能讓我們在需要的時候,隨意地加入代碼呢。這種在運行時,動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。
一般而言,我們管切入到指定類指定方法的程式碼片段稱為切面,而切入到哪些類、哪些方法則叫切入點。有了AOP,我們就可以把幾個類共有的代碼,抽取到一個切片中,等到需要時再切入對象中去,從而改變其原有的行為。
這樣看來,AOP其實只是OOP的補充而已。OOP從橫向上區分出一個個的類來,而AOP則從縱向上向對象中加入特定的代碼。有了AOP,OOP變得立體了。如果加上時間維度,AOP使OOP由原來的二維變為三維了,由平面變成立體了。從技術上來說,AOP基本上是通過代理機制實現的。
AOP在編程曆史上可以說是裡程碑式的,對OOP編程是一種十分有益的補充。


Demo介紹

1.全域打點上報單獨抽離

2.頁面首次進入重新整理

3.給頁面動態添加需要控制項

4.維護全域堆棧

這裡給的Demo只是一個簡單的思路

首先我們的核心思路針對控制器的周期事件進行hook,這裡可以提供兩種容器,一種是全域靜態容器儲存全域都公用的功能,還有一種是和ViewController綁定一個Category,每個控制器對應關聯一個數組,用來儲存需要的切片功能。切片其實就是一個具有某種功能的類,需要的時候注入到容器裡面就行了,在hook到生命週期的函數時,拿出來操作即可。


1.Hook交換方法

// 交換方法void MKJControllerLifeCircleSwizzInstance(Class class, SEL originalSelector, SEL swizzledSelector){    Method originalMethod = class_getInstanceMethod(class, originalSelector);    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);        BOOL didAddMethod =    class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));        if (didAddMethod)    {        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));    }    else    {        method_exchangeImplementations(originalMethod, swizzledMethod);    }}+ (void)load{    Class viewControllerClass = [UIViewController class];    MKJControllerLifeCircleSwizzInstance(viewControllerClass,@selector(viewDidAppear:),@selector(mkj_hook_viewDidAppear:));    MKJControllerLifeCircleSwizzInstance(viewControllerClass, @selector(viewDidDisappear:), @selector(mkj_hook_viewDidDisappear:));    MKJControllerLifeCircleSwizzInstance(viewControllerClass, @selector(viewWillAppear:), @selector(mkj_hook_viewWillAppear:));    MKJControllerLifeCircleSwizzInstance(viewControllerClass, @selector(viewWillDisappear:), @selector(mkj_hook_viewWillDisappear:));}


2.全域容器和ViewController的關聯容器

// 全域Action儲存的懶載入NSMutableArray * MKJViewControllerGlobalAction() {    static NSMutableArray *mutableArray = nil;    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        mutableArray = [[NSMutableArray alloc] init];    });    return mutableArray;}// 全域註冊到單利數組中void MKJViewControllerRegisterGlobalAction(MKJViewLifeCircleBaseAction *action){    void(^Register)(void) = ^(void) {        NSMutableArray* actions = MKJViewControllerGlobalAction();        if (![actions containsObject:action]) {            [actions addObject:action];        }    };        if ([NSThread mainThread]) {        Register();    } else    {        dispatch_async(dispatch_get_main_queue(), ^{            Register();        });    }}// 移除void MKJViewControllerRemoveGlobalAction(MKJViewLifeCircleBaseAction *action){    void(^Remove)(void) = ^(void) {        NSMutableArray* actions = MKJViewControllerGlobalAction();        if ([actions containsObject:action]) {            [actions removeObject:action];        }    };    if ([NSThread mainThread]) {        Remove();    } else {        dispatch_async(dispatch_get_main_queue(), ^{            Remove();        });    }}

/ Category關聯屬性- (void)setLifeCircleAction:(NSArray *)array{    objc_setAssociatedObject(self, MKJViewAppearKey, array, OBJC_ASSOCIATION_RETAIN);}- (NSArray *)getLifeCircleAction{    NSArray *array = objc_getAssociatedObject(self, MKJViewAppearKey);    // 如果第一次沒有擷取到 是 nil if不會成立    if ([array isKindOfClass:[NSArray class]]) {        return array;    }    return [NSArray array];}// 根據頁面註冊  把需要切入的操作綁定 掛載到對應的VC上面,這個不是全域的,是根據VC綁定的數組的- (MKJViewLifeCircleBaseAction* )registerLifeCircleAction:(MKJViewLifeCircleBaseAction *)action{    NSMutableArray* array = [NSMutableArray arrayWithArray:[self getLifeCircleAction]];    if ([array containsObject:action]) {        for (MKJViewLifeCircleBaseAction* act in array) {            if ([action isEqual:act]) {                return act;            }        }    }    [array addObject:action];    [self setLifeCircleAction:array];    return action;}// 移除- (void) removeLifeCircleAction:(MKJViewLifeCircleBaseAction *)action{    NSArray* array = [self getLifeCircleAction];    NSInteger index = [array indexOfObject:action];    if (index == NSNotFound) {        return;    }    NSMutableArray* mArray = [NSMutableArray arrayWithArray:array];    [mArray removeObjectAtIndex:index];    [self setLifeCircleAction:array];}
3.Hook執行方法
// 四個 hook出來的方法- (void)mkj_hook_viewWillAppear:(BOOL)animated{    [self mkj_hook_viewWillAppear:animated];    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {        if ([action respondsToSelector:@selector(hookViewController:viewWillAppear:)]) {            [action hookViewController:self viewWillAppear:animated];        }    }];}- (void)mkj_hook_viewDidAppear:(BOOL)animated{    [self mkj_hook_viewDidAppear:animated];    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {        if ([action respondsToSelector:@selector(hookViewController:viewDidAppear:)]) {            [action hookViewController:self viewDidAppear:animated];        }    }];}- (void)mkj_hook_viewWillDisappear:(BOOL)animated{    [self mkj_hook_viewWillDisappear:animated];    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {        if ([action respondsToSelector:@selector(hookViewController:viewWillDisappear:)]) {            [action hookViewController:self viewWillDisappear:animated];        }    }];}- (void)mkj_hook_viewDidDisappear:(BOOL)animated{    [self mkj_hook_viewDidDisappear:animated];    [self mkj_hook_performAction:^(MKJViewLifeCircleBaseAction *action) {        if ([action respondsToSelector:@selector(hookViewController:viewDidDisappear:)]) {            [action hookViewController:self viewDidDisappear:animated];        }    }];}/** Hook的方法執行代碼 會先擷取全域容器和關聯的容器進行遍曆,然後執行儲存在裡面的切片Action @param block 回調 */- (void)mkj_hook_performAction:(MKJAOPActionBlock)block{    // 1.擷取全域Action  單利    MKJCallBackAction([MKJViewControllerGlobalAction() copy], block);        // 2.擷取掛載的Action  根據頁面儲存的數組    MKJCallBackAction([[self getLifeCircleAction] copy], block);}// 執行 Actionvoid MKJCallBackAction(NSArray* actions, MKJAOPActionBlock block) {    for (MKJViewLifeCircleBaseAction* action in actions) {        if (block) {            block(action);        }    }}

4.建立切片Action

根據上面Hook到的函數,實際上是取出之前注入到容器中的Action,然後執行Action的方法,全域容器是無論哪個控制器都有管理,而可選關聯的容器是根據頁面綁定的,因此觸發的時候把容器self傳出去即可

/** 基類 所有切片的基類 通過vc的周期函數進行處理 */@interface MKJViewLifeCircleBaseAction : NSObject@property (nonatomic,strong,readonly) UIViewController *liveViewController;- (void)hookViewController:(UIViewController *)controller viewWillAppear:(BOOL)animated;- (void)hookViewController:(UIViewController *)controller viewDidAppear:(BOOL)animated;- (void)hookViewController:(UIViewController *)controller viewWillDisappear:(BOOL)animated;- (void)hookViewController:(UIViewController *)controller viewDidDisappear:(BOOL)animation;@end

5.全域切片

全域切片是不需要在外面寫任何處理的代碼,直接在本類中load加入到資料就可以回調

@implementation MKJViewControllerLogAction// 全域註冊+ (void)load{    // 註冊到全域靜態數組裡面,每個頁面周期函數觸發hook都會進入這裡列印log    MKJViewControllerRegisterGlobalAction([[MKJViewControllerLogAction alloc] init]);}// 這個Action可以用來做全域列印,方便使用,在load裡面可以拿掉或者注入,完全不會影響商務邏輯以及汙染代碼- (void)hookViewController:(UIViewController *)controller viewWillAppear:(BOOL)animated{    [super hookViewController:controller viewWillAppear:animated];    NSLog(@"ViewController---->%@-------SEL------>%@",controller,NSStringFromSelector(_cmd));}- (void)hookViewController:(UIViewController *)controller viewDidAppear:(BOOL)animated{    [super hookViewController:controller viewDidAppear:animated];}- (void)hookViewController:(UIViewController *)controller viewWillDisappear:(BOOL)animated{    [super hookViewController:controller viewWillDisappear:animated];    NSLog(@"ViewController---->%@-------SEL------>%@",controller,NSStringFromSelector(_cmd));}- (void)hookViewController:(UIViewController *)controller viewDidDisappear:(BOOL)animation{    [super hookViewController:controller viewDidDisappear:animation];}@end

6.專用切片Action(可選)

專用職責根據上面的容器,是根據頁面關聯的數組,因此需要在頁面上添加小部分註冊的代碼

- (instancetype)initWithActionBlock:(MKJViewControllerFirstAppearBlock)block{    self = [super init];    if (self) {        _isFirstAppear = YES;        _actionBlock = block;    }    return self;}- (void)hookViewController:(UIViewController *)controller viewWillAppear:(BOOL)animated{    [super hookViewController:controller viewWillAppear:animated];    if (_isFirstAppear) {        if (_actionBlock) {            _actionBlock(controller,animated);        }        _isFirstAppear = NO;    }    }
MKJVIewControllerFirstEnterAction *firstAction = [[MKJVIewControllerFirstEnterAction alloc] initWithActionBlock:^(ViewController *controller, BOOL animation) {        [controller pop:animation];    }];    [self registerLifeCircleAction:firstAction];


這裡順著一個博主的思路和代碼寫了一下分析了一下,基本邏輯就很清晰的

底層切片邏輯

基於底層切片,控制器跳轉用切片實現,用切面維護一個全域堆棧


更新:

上述AOP實現是基於上層SEL和IMP的交換,也是最簡單的方式

網上有一個輪子是基於Runtime訊息轉寄截獲的,把每個訊息都動態指向objc_msgForward進入轉寄流程來實現

Aspects




相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.