iOS AOP實戰


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類方法的支持,希望能夠幫助大家~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM