Portal: AopTestDemo

1. Scenario requirements

  • Count the number of UIViewController loads
  • Count the number of UIButton clicks
  • Statistics the execution of custom methods
  • Count Cell click events for UITableView

Click the first button to print. The second to fourth buttons jump to Test2ViewController, Test3ViewController, and Test4ViewController respectively.

Technical selection:

  • The code logic for manually copying statistics is pasted one by one into the classes and methods that need statistics. Heavy workload, poor maintainability, only applicable to the situation of few statistical buried points.
  • By inheriting and overwriting system methods – using a base class that writes statistics, having VC inherit from that base class, or calling a button base class that overwrites statistics logic, and so on.
  • Simple categorization, add class methods or sample methods – encapsulate statistical logic in categorization methods, import and call categorization methods where statistics are needed.
  • Classification of replacement system methods: methods are replaced by the Method Swizzling mechanism through the Runtime: methods that require statistics but do not contain statistical logic are replaced by new methods that contain statistical logic.
  • Using AOP’s approach — the Aspect framework is used to hook the methods that need to be counted and inject code blocks that contain statistical logic.

2. Classification designed for VC: runtime Method Swizzling scheme

Scenario requirement: The same method that needs to listen on a class globally

This scheme listens for a single method, but affects that method globally for all classes. For example, the following categories, even if you don’t import them, will be affected as long as they exist in the project.

  • UIViewController+Trace
#import "UIViewController+Trace.h"
#import "TraceHandler.h"
#import <objc/runtime.h>
#import <objc/objc.h>
#import "Aspects.h"


@implementation UIViewController (Trace)

Pragma mark - 1+ (void)load{ swizzleMethod([self class], @selector(viewDidAppear:), @selector(swizzled_viewDidAppear:)); } - (void)swizzled_viewDidAppear:(BOOL)animated{ // call original implementation [self swizzled_viewDidAppear:animated];  // Begin statistics Event [TraceHandler statisticsWithEventName:@"UIViewController"];
}

void swizzleMethod(Class class,SEL originalSelector,SEL swizzledSelector){
    // the method might not exist in the class, but in its superclass
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    // class_addMethod will fail if original method already exists
    BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

    // the method doesn’t exist and we just added one
    if (didAddMethod) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    }
    else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

@end
Copy the code
  • TraceHandler.m
#import "TraceHandler.h"

@implementation TraceHandler

+ (void)statisticsWithEventName:(NSString *)eventName{
    NSLog(@"-- -- -- -- - > % @",eventName);
}

@end
Copy the code

3. Classification designed for VC: AOP programming scheme

Scenario requirements: The applicable characteristics of this scheme are the same as the second section above.

Aspects is a lightweight aspect oriented programming (AOP) framework for the iOS platform that consists of just two methods: a class method and an instance method.

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
Copy the code
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;
Copy the code

Functions are easy to use and hook in three ways:

Typedef NS_OPTIONS(NSUInteger, AspectOptions) {AspectPositionAfter = 0, /// AspectPositionInstead = 1, / / / replace the original method AspectPositionBefore = 2, / / / in front of the original method call AspectOptionAutomaticRemoval = 1 < < 3 / / / after performing one automatically remove};Copy the code

Call sample code:

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
Copy the code

This code gives UIViewController viewWillAppear: hook a Block that prints a string after the original method has executed.

  • UIViewController+Trace
#pragma Mark - 2. Use Aspects framework+ (void)load{ [UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo>aspectInfo){ NSString *className = NSStringFromClass([[aspectInfo instance] class]);;  [TraceHandler statisticsWithEventName:className]; } error:nil]; }Copy the code

4. Classification for global AppDelegate design: AOP programming scheme

Scenario requirements: Need to listen for different classes, buttons, system methods, and form-element click events

Program features: is code configurable to listen to the list dictionary, and the need to inject statistical code blocks can be written in this list.

  • AppDelegate+Trace.m
#import "AppDelegate+Trace.h"
#import "TraceManager.h"

@implementation AppDelegate (Trace)

+ (void)setupLogging{
    NSDictionary *configDic = @{
                                @"ViewController": @ {@"des": @"show ViewController", @}"Test1ViewController": @ {@"des": @"show Test1ViewController"The @"TrackEvents": @ [@ {@"EventDes": @"click action1"The @"EventSelectorName": @"action1"The @"block":^(id<AspectInfo>aspectInfo){
                                                                 NSLog(@Test1ViewController Action1 Click Event); }}, @ {@"EventDes": @"click action2"The @"EventSelectorName": @"action2"The @"block":^(id<AspectInfo>aspectInfo){
                                                                 NSLog(@Test1ViewController Action2 Click Statistics); }}],}, @"Test2ViewController": @ {@"des": @"show Test2ViewController",}}; [TraceManagersetUpWithConfig:configDic];
}

@end
Copy the code
  • TraceManager.m
#import "TraceManager.h"

@import UIKit;

typedef void (^AspectHandlerBlock)(id<AspectInfo> aspectInfo);

@implementation TraceManager

+ (void)setUpWithConfig:(NSDictionary *)configDic{// hook all page viewDidAppear events [UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *className = NSStringFromClass([[aspectInfo instance] class]); NSString *des = configDic[className][@"des"];
                                       if (des) {
                                           NSLog(@"% @",des); }}); } error:NULL];for (NSString *className in configDic) {
        Class clazz = NSClassFromString(className);
        NSDictionary *config = configDic[className];
        
        if (config[@"TrackEvents"]) {
            for (NSDictionary *event in config[@"TrackEvents"]) {
                SEL selekor = NSSelectorFromString(event[@"EventSelectorName"]);
                AspectHandlerBlock block = event[@"block"];
                
                [clazz aspect_hookSelector:selekor
                               withOptions:AspectPositionAfter
                                usingBlock:^(id<AspectInfo> aspectInfo){
                                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                                        block(aspectInfo);
                                    });
                                }error:NULL];
            }
        }
    }
}

@end
Copy the code

5. HOOK the Plist listener list in the AppDelegate class method

Scenario requirements: Need to listen for different classes, buttons, system methods, and form-element click events

Program features: can be configured to listen to the list of Plist, but can not be injected into the statistical code block written in the list of Plist.

  • EventList.plist

  • AspectMananer.m
Pragma mark - Monitors button click events+ (void)trackBttonEvent{ __weak typeof(self) ws = self; // Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{ Get a list of events to be counted NSString *path = [[NSBundle mainBundle] pathForResource:@"EventList" ofType:@"plist"];
        NSDictionary *eventStatisticsDict = [[NSDictionary alloc] initWithContentsOfFile:path];
        for (NSString *classNameString inEventStatisticsDict. AllKeys) {/ / use the runtime class object creation const char * className = [classNameString UTF8String]; // Return a Class newClass = objc_getClass(className) from a string; NSArray *pageEventList = [eventStatisticsDict objectForKey:classNameString];for (NSDictionary *eventDict inPageEventList) {// eventMethodName NSString *eventMethodName = eventDict[@"MethodName"];
                SEL seletor = NSSelectorFromString(eventMethodName);
                NSString *eventId = eventDict[@"EventId"]; [ws trackEventWithClass:newClass selector:seletor eventID:eventId]; [ws trackTableViewEventWithClass:newClass selector:seletor eventID:eventId]; [ws trackParameterEventWithClass:newClass selector:seletor eventID:eventId]; }}}); }#pragma mark -- 1. Monitor button and tap click events (no arguments)
+ (void)trackEventWithClass:(Class)klass selector:(SEL)selector eventID:(NSString*)eventID{
    
    [klass aspect_hookSelector:selector withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
        
        NSString *className = NSStringFromClass([aspectInfo.instance class]);
        NSLog(@"className--->%@",className);
        NSLog(@"event----->%@",eventID);
        if ([eventID isEqualToString:@"xxx"]) { // [EJServiceUserInfo isLogin]? [MobClick event:eventID]:[MobClick event:@"?????"];
        }else{
//            [MobClick event:eventID];
        }
    } error:NULL];
}


#pragma mark -- 2. Monitor button and tap click events (with parameters)
+ (void)trackParameterEventWithClass:(Class)klass selector:(SEL)selector eventID:(NSString*)eventID{
    
    [klass aspect_hookSelector:selector withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo,UIButton *button) {
        
        NSLog(@"button---->%@",button);
        NSString *className = NSStringFromClass([aspectInfo.instance class]);
        NSLog(@"className--->%@",className);
        NSLog(@"event----->%@",eventID);
        
    } error:NULL];
}


#pragma Mark -- 3. Monitor tableView click events
+ (void)trackTableViewEventWithClass:(Class)klass selector:(SEL)selector eventID:(NSString*)eventID{
    
    [klass aspect_hookSelector:selector withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo,NSSet *touches, UIEvent *event) {
        
        NSString *className = NSStringFromClass([aspectInfo.instance class]);
        NSLog(@"className--->%@",className);
        NSLog(@"event----->%@",eventID);
        NSLog(@"section---->%@",[event valueForKeyPath:@"section"]);
        NSLog(@"row---->%@",[event valueForKeyPath:@"row"]);
        NSInteger section = [[event valueForKeyPath:@"section"]integerValue];
        NSInteger row = [[event valueForKeyPath:@"row"]integerValue]; // Statistics eventsif (section == 0 && row == 1) {
//            [MobClick event:eventID];
        }
        
    } error:NULL];
}
Copy the code
  • Appdelegate. M calls
[AspectMananer trackBttonEvent];
Copy the code