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