iOS 使用AOP统计打点

统计打点是 App 开发里很重要的一个环节,App 的运行状态、用户的各种行为等都需要打点,有不少关于统计的第三方库(如友盟统计)。但是,如果要求在整个项目的所有button里统计用户的点击事件,假如一个项目里面有1000个button,你就要设1000个地方设置统计代码,显然不科学。
有没有一种办法在一个地方进行统计打点,检测整个应用的点击事件?

方案一:使用Runtime的方式追踪点击的按钮

特点:需要对每个button进行tag编号,对手势点击、tableView的点击要单独配置,比较繁琐

方案二:使用面向切面编程AOP对按钮或者页面进行追踪(无需在任何详情页面中做相应配置)

特点:

1、在不修改源代码的情况下,通过运行时给程序添加统一功能的技术,可以用作日志记录,性能统计等
2、无需对每个button进行tag编号,创建button后只需在新建的plist中配置button对应的方法名和对应的事件 ID就行
3、适用于Tap点击手势,使用时设置事件ID,和button的使用方法一样
4、button不支持直接在block里面写事件的方式,但可以在block里面调用方法或者需要统一写成下面的方式

[button addTarget:self  action:@selector(click)forControlEvents:UIControlEventTouchUpInside];

5、适用于tableview的didSelectRowAtIndexPath点击事件,可获取tableView对应的类名、section和row
6、如果是统计tableview的点击事件,根据需要在获取到section和row后加个判断埋点统计

if (section == 0 && row == 1) {
   [MobClick event:eventID];
}

7、如果有特殊需求:某个按钮登录前和登录后记录的事件不一样,需要加判断

if ([eventID isEqualToString:@"xxx"]) {
    [EJServiceUserInfo isLogin]?[MobClick event:eventID]:[MobClick event:@"???"];
   }else{
   [MobClick event:eventID];
 }

以下是具体代码:

(不得不吐槽一下,网上很多博客文章都是转载的,很少有能直接运行的,研究了一整天才弄出来)

这里用到了第三方库:Aspects,用cocoaPods进行集成 pod 'Aspects'

1、创建一个继承与NSObject的EJAspectManager类

EJAspectManager.h

@interface EJAspectManager : NSObject
+(void)trackAspectHooks;
@end

EJAspectManager.m

#import "EJAspectManager.h"
#import "Aspects/Aspects.h"
@implementation EJAspectManager

+(void)trackAspectHooks{

    [EJAspectManager trackViewAppear];
    [EJAspectManager trackBttonEvent];
}


#pragma mark -- 监控统计用户进入此界面的时长,频率等信息
+ (void)trackViewAppear{
    
    [UIViewController aspect_hookSelector:@selector(viewWillAppear:)
                              withOptions:AspectPositionBefore
                               usingBlock:^(id<AspectInfo> info){
                                   
                                   //用户统计代码写在此处
                                   DDLogDebug(@"[打点统计]:%@ viewWillAppear",NSStringFromClass([info.instance class]));
                                   NSString *className = NSStringFromClass([info.instance class]);
                                   DLog(@"className-->%@",className);
                                   [MobClick beginLogPageView:className];//(className为页面名称
                                   
                               }
                                    error:NULL];
    
    
    [UIViewController aspect_hookSelector:@selector(viewWillDisappear:)
                              withOptions:AspectPositionBefore
                               usingBlock:^(id<AspectInfo> info){
                                   
                                   //用户统计代码写在此处
                                   DDLogDebug(@"[打点统计]:%@ viewWillDisappear",NSStringFromClass([info.instance class]));
                                   NSString *className = NSStringFromClass([info.instance class]);
                                   DLog(@"className-->%@",className);
                                   [MobClick endLogPageView:className];
                                   
                               }
                                    error:NULL];
    
    //other hooks ... goes here
    //...
}

#pragma mark --- 监控button的点击事件
+ (void)trackBttonEvent{
    
    __weak typeof(self) ws = self;

    //设置事件统计
    //放到异步线程去执行
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //读取配置文件,获取需要统计的事件列表
        NSString *path = [[NSBundle mainBundle] pathForResource:@"EventList" ofType:@"plist"];
        NSDictionary *eventStatisticsDict = [[NSDictionary alloc] initWithContentsOfFile:path];
        for (NSString *classNameString in eventStatisticsDict.allKeys) {
            //使用运行时创建类对象
            const char * className = [classNameString UTF8String];
            //从一个字串返回一个类
            Class newClass = objc_getClass(className);
            
            NSArray *pageEventList = [eventStatisticsDict objectForKey:classNameString];
            for (NSDictionary *eventDict in pageEventList) {
                //事件方法名称
                NSString *eventMethodName = eventDict[@"MethodName"];
                SEL seletor = NSSelectorFromString(eventMethodName);
                NSString *eventId = eventDict[@"EventId"];
                
                [ws trackEventWithClass:object_getClass(newClass) selector:seletor eventID:eventId];
                [ws trackTableViewEventWithClass:object_getClass(newClass) selector:seletor eventID:eventId];
                [ws trackParameterEventWithClass:object_getClass(newClass) selector:seletor eventID:eventId];
            }
        }
    });
}

#pragma mark -- 监控button和tap点击事件(不带参数)
+ (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 -- 监控button和tap点击事件(带参数)
+ (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 -- 监控tableView的点击事件
+ (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];
        
        //统计事件
        if (section == 0 && row == 1) {
            [MobClick event:eventID];
        }
        
    } error:NULL];
}
@end

2、这样我们在appDelegate里面直接调用下面的代码就可以达到全局统计的目的了!

[EJAspectManager trackAspectHooks];

3、上面涉及到的EventPlist是新创建的plist文件,设置方式如下:

B091368F-0F17-4671-9A5E-F6527BA5CD31.png

需要demo的同学麻烦打个赏(金额随便给),留个邮箱,我会发到你们邮箱里,谢谢支持!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 背景 业务扩展的需要,对用户行为数据的收集和分析也就日益重要,前期实现的打点方案只能使用在单一app客户端中,无法...
    littlewish阅读 10,381评论 1 12
  • 文章来源 方案一:使用Runtime的方式追踪点击的按钮 特点:需要对每个button进行tag编号,对手势点击、...
    程序员不务正业阅读 3,870评论 0 1
  • 概念 AOP编程也叫面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的...
    _冷忆阅读 4,474评论 0 2
  • 之前到别人的一篇博客上评论了下他的打点统计方法,后来很多人来问我,,所以还是决定写下这篇文章。第一次写博客,不...
    yy倚楼听风雨阅读 10,061评论 5 26
  • AOP(Aspect Oriented Programming)面向切面编程 相比传统的OOP来说,OOP的特点在...
    阳明AI阅读 3,192评论 0 3