AOP: 面向切麵編程,偏向於處理業務的某個階段 適用場景: 1. 參數校驗:網路請求前的參數校驗,返回數據的格式校驗等等 2. 無痕埋點:統一處理埋點,降低代碼耦合度 3. 頁面統計:幫助統計頁面訪問量 4. 事務處理:攔截指定事件,添加觸發事件 5. 異常處理:發生異常時使用面向切麵的方式進行處 ...
AOP: 面向切麵編程,偏向於處理業務的某個階段
適用場景:
1. 參數校驗:網路請求前的參數校驗,返回數據的格式校驗等等
2. 無痕埋點:統一處理埋點,降低代碼耦合度
3. 頁面統計:幫助統計頁面訪問量
4. 事務處理:攔截指定事件,添加觸發事件
5. 異常處理:發生異常時使用面向切麵的方式進行處理
6. 熱修複:AOP可以讓我們在某方法執行前後或者直接替換為另一段代碼,我們可以根據這個思路,實現bug修複
我們希望將以上需求分離到非業務邏輯的方法中,儘可能的不影響業務邏輯的代碼。
demo 從配置AOP到實際應用,有空給咱點個star~
源碼分析
0. 類說明
MDAspectInfo:作為對象,包含調用信息(NSInvocation)的對象
作為協議,提供訪問對象的屬性
MDAspectIdentifier:包含一個hook的信息,調用者,時機,回調處理等
MDAspectTracker:防止重覆hook
MDAspectsContainer:通過runtime給被hook的對象添加屬性,提供存儲和移除hook的方法
MDAspectToken:提供移除hook的協議
1. hook時機
typedef NS_OPTIONS(NSUInteger, MDAspectOptions) { MDAspectPositionAfter = 0, /// 預設,當原方法執行完調用 MDAspectPositionInstead = 1, /// 替換原方法 MDAspectPositionBefore = 2, /// 原方法執行前調用 MDAspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution. };
2. 配置文件
配置hook的類,hook時機,實例方法和類方法,以及回調處理
為了區分實例方法和類方法,需要在類方法前加一個“+”
+(NSDictionary *)AOP_MDViewControllerConfigDic{ NSDictionary *configDic = @{ @"MDViewController":@{//hook那個類名 @"TrackEvents":@[ @{//實例方法 @"moment":@"before",//hook之前調用 @"EventSelectorName":@"instanceMethod",//實例方法名 @"block":^(id<MDAspectInfo>aspectInfo){//回調處理 // 獲取方法的參數 NSLog(@"跳轉"); }, }, @{//類方法 @"moment":@"instead",//替換原方法 @"EventSelectorName":@"+hookClassMethod",//類方法名 @"block":^(id<MDAspectInfo>aspectInfo){//回調處理 // 獲取方法的參數 NSLog(@"到處可以hook到我"); }, }, ] }, }; return configDic; }
3. 解析管理類
// hook到方法回調,完全控制 typedef void (^AspectEventBlock)(id<MDAspectInfo> aspectInfo); @implementation MDAOPManager +(void)load{ // 載入配置文件 NSMutableDictionary *mutableDic = [NSMutableDictionary dictionary]; [mutableDic addEntriesFromDictionary:[MDAOPManager AOP_MDViewControllerConfigDic]]; [mutableDic addEntriesFromDictionary:[MDAOPManager AOP_MDSecViewControllerConfigDic]]; [self configAOPWithDic:mutableDic]; } +(void)configAOPWithDic:(NSDictionary *)configDic{ // 解析配置文件 for (NSString *className in configDic) { Class clazz = NSClassFromString(className);//拿到類名 NSDictionary *config = configDic[className];//配置信息 NSArray *trackArr = config[@"TrackEvents"];//方法數組 if (trackArr) { for (NSDictionary *event in trackArr) { AspectEventBlock buttonBlock = event[@"block"];//回調 NSString *method = event[@"EventSelectorName"];//方法名 NSString *moment = event[@"moment"];//hook時機 MDAspectOptions option = MDAspectPositionAfter; if ([moment isEqualToString:@"before"]) { option = MDAspectPositionBefore; }else if ([moment isEqualToString:@"instead"]){ option = MDAspectPositionInstead; } SEL selector = NSSelectorFromString(method); if ([method hasPrefix:@"+"]) {//hook類方法 method = [method substringFromIndex:1]; selector = NSSelectorFromString(method); [clazz aspect_hookClassSelector:selector withOptions:option usingBlock:^(id<MDAspectInfo> aspectInfo) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ buttonBlock(aspectInfo); }); } error:NULL]; }else{//hook實例方法 [clazz aspect_hookSelector:selector withOptions:option usingBlock:^(id<MDAspectInfo> aspectInfo) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ buttonBlock(aspectInfo); }); } error:NULL]; } } } } }
4. 對外介面
// 類直接調用,hook實例方法
+ (id<MDAspectToken>)aspect_hookSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError **)error;
// 對象調用,hook實例方法
- (id<MDAspectToken>)aspect_hookSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError **)error;
// 類直接調用,hook類方法
+ (id<MDAspectToken>)aspect_hookClassSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError *__autoreleasing *)error;
// 對象調用,hook類方法
- (id<MDAspectToken>)aspect_hookClassSelector:(SEL)selector withOptions:(MDAspectOptions)options usingBlock:(id)block error:(NSError *__autoreleasing *)error;
說明:MDAspect是對Aspects的擴展,添加了hook類方法的支持,希望能夠幫助大家~