iOS基于友盟事件统计封装

统计分析管理类,包含对所有ViewController添加页面统计方法、对单一ViewController添加页面统计方法以及手动添加事件的相关方法。需要添加友盟库

#import <UIKit/UIKit.h>

@interface RSAppAnalyticsManager : NSObject


/**
 集成测试设备id,测试阶段需在UM平台->管理->集成测试中添加deviceId

 @return UM deviceId
 */
+ (NSString*)RS_debugDeviceId;
/**
 初始化统计功能

 @param appKey UM appKey
 @param channel 频道(nil:App Store)
 @param open 是否打开崩溃信息收集
 */
+ (void)RS_initAppAnalyticsWithAppKey:(NSString*)appKey channel:(NSString *)channel openCrashReport:(BOOL)open;

/**
 对指定控制器添加计算统计

 ---*********注意*不可与全局页面计算统计功能(RS_addAnalyticsForAllViewController)同时使用,会造成死循环*********-----
 
 @param viewController 目标控制器
 */
+ (void)RS_addAnalyticsForViewController:(UIViewController*)viewController;

/**
 添加全局ViewController页面计算统计
 */
+(void)RS_addAnalyticsForAllViewController;

/**
 是否开启日志功能(release下自动设置NO)

 @param enabled 是否打开日志功能;debug状态有效
 */
+ (void)RS_setLogEnabled:(BOOL)enabled;

/*----------------------计数事件-----------------------*/
+ (void)RS_event:(NSString *)eventId;
+ (void)RS_event:(NSString *)eventId label:(NSString *)label;
+ (void)RS_event:(NSString *)eventId attributes:(NSDictionary *)attributes;
+ (void)RS_event:(NSString *)eventId attributes:(NSDictionary *)attributes counter:(int)number;

/*----------------------计算事件-----------------------*/
+ (void)RS_beginEvent:(NSString *)eventId;
+ (void)RS_endEvent:(NSString *)eventId;
+ (void)RS_beginEvent:(NSString *)eventId label:(NSString *)label;
+ (void)RS_endEvent:(NSString *)eventId label:(NSString *)label;
+ (void)RS_beginEvent:(NSString *)eventId primarykey :(NSString *)keyName attributes:(NSDictionary *)attributes;
+ (void)RS_endEvent:(NSString *)eventId primarykey:(NSString *)keyName;
+ (void)RS_event:(NSString *)eventId durations:(int)millisecond;
+ (void)RS_event:(NSString *)eventId label:(NSString *)label durations:(int)millisecond;
+ (void)RS_event:(NSString *)eventId attributes:(NSDictionary *)attributes durations:(int)millisecond;

/*----------------------页面统计-----------------------*/
+ (void)RS_logPageView:(NSString *)pageName seconds:(int)seconds;
+ (void)RS_beginLogPageView:(NSString *)pageName;
+ (void)RS_endLogPageView:(NSString *)pageName;
@end
#import "RSAppAnalyticsManager.h"
#import <UMCommon/UMCommon.h>
#import <UMAnalytics/MobClick.h>
#ifdef DEBUG
    #import <UMCommonLog/UMCommonLogHeaders.h>
#endif

#import <objc/message.h>

static NSString* const kSUBCLASS_PREFIX_NAME =  @"RSAnalytics_Target_ViewController";
@implementation RSAppAnalyticsManager

#pragma mark  - public
+ (NSString*)RS_debugDeviceId{
    return [UMConfigure deviceIDForIntegration];
}

+ (void)RS_initAppAnalyticsWithAppKey:(NSString*)appKey channel:(NSString *)channel openCrashReport:(BOOL)open{
    //开启统计数据加密传输
    [UMConfigure setEncryptEnabled:YES];
    [MobClick setCrashReportEnabled:open];
    [UMConfigure initWithAppkey:appKey channel:channel];
}

+ (void)RS_addAnalyticsForViewController:(UIViewController*)viewController{
    if (viewController == nil || ![viewController isKindOfClass:[UIViewController class]]) {
        return;
    }
    NSString* className = NSStringFromClass([viewController class]);
    NSString* subClassName = [kSUBCLASS_PREFIX_NAME stringByAppendingString:className];
    Class subClass = NSClassFromString(subClassName);
    if (subClass == nil) {
        /*创建子类,在子类中添加方法下面两个方法,确保控制器实现了这两个方法*/
        subClass = objc_allocateClassPair([viewController class], subClassName.UTF8String, 0);
        objc_registerClassPair(subClass);
        /*向子类中添加viewWillAppear:和viewWillDisappear:方法,防止jviewController中未实现这两个方法,而导致接下来交换方法实现交换成UIViewController的两个方法,如果交换的是UIViewController的两个方法,则会作用全局,并且全局中其他控制器没有交换到先添加的方法,导致程序奔溃*/
        [self _RS_addViewWillAppearAndDisappearForSubClass:subClass formViewController:viewController];
    }
    //修改viewController的isa指针
    object_setClass(viewController, subClass);
    
    SEL rsViewWillAppearSEL = @selector(_RSAnalytics_viewWillAppear:);
    ///获取IMP的两种方法
//1.    Method rsViewWillAppear = class_getInstanceMethod([self class], rsViewWillAppearSEL);
       //IMP rsIMP = method_getImplementation(rsViewWillAppear);
    
//2.    IMP rsIMP = class_getMethodImplementation([self class], rsViewWillAppearSEL)
    if (class_addMethod([viewController class], rsViewWillAppearSEL, class_getMethodImplementation([self class], rsViewWillAppearSEL), "v@:B")) {
        Method viewWillAppear = class_getInstanceMethod([viewController class],@selector(viewWillAppear:));
        Method newWillAppearMethod = class_getInstanceMethod([viewController class], rsViewWillAppearSEL);
        method_exchangeImplementations(viewWillAppear, newWillAppearMethod);
    }
    
    SEL rsviewWillDisappearSel = @selector(_RSAnalytics_viewWillDisappear:);
    if (class_addMethod([viewController class], rsviewWillDisappearSel, class_getMethodImplementation([self class], rsviewWillDisappearSel), "v@:B")) {
        Method viewWllDisappear = class_getInstanceMethod([viewController class], @selector(viewWillDisappear:));
        Method newWillDisappearrMethod = class_getInstanceMethod([viewController class], rsviewWillDisappearSel);
        method_exchangeImplementations(viewWllDisappear, newWillDisappearrMethod);
    }
}
//依赖UIViewController+Analytics分类
+ (void)RS_addAnalyticsForAllViewController{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method viewWillAppear = class_getInstanceMethod([UIViewController class],@selector(viewWillAppear:));
        Method rsViewWillAppear = class_getInstanceMethod([UIViewController class],  @selector(_RSAnalytics_viewWillAppear:));
        method_exchangeImplementations(viewWillAppear,rsViewWillAppear);

        Method viewWllDisappear = class_getInstanceMethod([UIViewController class], @selector(viewWillDisappear:));
        Method rsViewWillDisappear = class_getInstanceMethod([UIViewController class],@selector(_RSAnalytics_viewWillDisappear:));
        method_exchangeImplementations(viewWllDisappear, rsViewWillDisappear);
    });
}

+ (void)RS_setLogEnabled:(BOOL)enabled{
#ifdef DEBUG
    [UMCommonLogManager setUpUMCommonLogManager];
    [UMConfigure setLogEnabled:enabled];
#endif
}

/*----------------------计数事件-----------------------*/
+ (void)RS_event:(NSString *)eventId{
    [MobClick event:eventId];
}

+ (void)RS_event:(NSString *)eventId label:(NSString *)label{
    [MobClick event:eventId label:label];
}

+ (void)RS_event:(NSString *)eventId attributes:(NSDictionary *)attributes{
    [MobClick event:eventId attributes:attributes];
}

+ (void)RS_event:(NSString *)eventId attributes:(NSDictionary *)attributes counter:(int)number{
    [MobClick event:eventId attributes:attributes counter:number];
}
/*----------------------计算事件-----------------------*/
+ (void)RS_beginEvent:(NSString *)eventId{
    [MobClick beginEvent:eventId];
}

+ (void)RS_endEvent:(NSString *)eventId{
    [MobClick endEvent:eventId];
}

+ (void)RS_beginEvent:(NSString *)eventId label:(NSString *)label{
    [MobClick beginEvent:eventId label:label];
}

+ (void)RS_endEvent:(NSString *)eventId label:(NSString *)label{
    [MobClick endEvent:eventId label:label];
}

+ (void)RS_beginEvent:(NSString *)eventId primarykey :(NSString *)keyName attributes:(NSDictionary *)attributes{
    [MobClick beginEvent:eventId primarykey:keyName attributes:attributes];
}

+ (void)RS_endEvent:(NSString *)eventId primarykey:(NSString *)keyName{
    [MobClick endEvent:eventId primarykey:keyName];
}

+ (void)RS_event:(NSString *)eventId durations:(int)millisecond{
    [MobClick event:eventId durations:millisecond];
}

+ (void)RS_event:(NSString *)eventId label:(NSString *)label durations:(int)millisecond{
    [MobClick event:eventId label:label durations:millisecond];
}

+ (void)RS_event:(NSString *)eventId attributes:(NSDictionary *)attributes durations:(int)millisecond{
    [MobClick event:eventId attributes:attributes durations:millisecond];
}

/*----------------------页面统计-----------------------*/
+ (void)RS_logPageView:(NSString *)pageName seconds:(int)seconds{
    [MobClick logPageView:pageName seconds:seconds];
}

+ (void)RS_beginLogPageView:(NSString *)pageName{
    [MobClick beginLogPageView:pageName];
}

+ (void)RS_endLogPageView:(NSString *)pageName{
    [MobClick endLogPageView:pageName];
}

#pragma mark  - private
#pragma mark  - 待添加交换方法
- (void)_RSAnalytics_viewWillAppear:(BOOL)animated{
    NSString* pageName = [RSAppAnalyticsManager _RS_getViewControllerPageName:self];
    if (pageName && pageName.length > 0) {
        [RSAppAnalyticsManager RS_beginLogPageView:pageName];
    }
    [self _RSAnalytics_viewWillAppear:animated];
}

- (void)_RSAnalytics_viewWillDisappear:(BOOL)animated{
    NSString* pageName = [RSAppAnalyticsManager _RS_getViewControllerPageName:self];
    if (pageName && pageName.length > 0) {
       [RSAppAnalyticsManager RS_endLogPageView:pageName];
    }
    [self _RSAnalytics_viewWillDisappear:animated];
}

+ (NSString*)_RS_getViewControllerPageName:(id)vcController{
    UIViewController* controller = (UIViewController*)vcController;
    NSString* pageName = [controller respondsToSelector:@selector(title)] ? controller.title : nil;
    return pageName;
}

#pragma mark  - 为子类控制器类添加viewWillAppear:和viewWillDisappear:确保控制器一定实现了这两个方法,避免因未实现而交换UIViewController的方法实现,导致作用到全局

/**
 为类添加viewWillAppear:和viewWillDisappear:方法

 @param clazz 目标类
 @param viewController 方法来源控制器(即参照控制器添加一个相同的方法到目标类中,如果viewController没有则根据继承关系逐层查找,类似copy一个方法到目标类,不会影响原类)
 */
+ (void)_RS_addViewWillAppearAndDisappearForSubClass:(Class)clazz formViewController:(UIViewController*)viewController{
    SEL addWillAppearSel = @selector(viewWillAppear:);
    class_addMethod(clazz, addWillAppearSel, method_getImplementation(class_getInstanceMethod([viewController class], addWillAppearSel)), "v@:B");
    
    SEL addWillDisappearSel = @selector(viewWillDisappear:);
    class_addMethod(clazz, addWillDisappearSel, method_getImplementation(class_getInstanceMethod([viewController class], addWillDisappearSel)), "v@:B");
}
@end

对所有ViewController添加页面统计方法的辅助分类

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

/**
 为设置全局页面计算统计提供支撑
 */
@interface UIViewController (Analytics)

@end

NS_ASSUME_NONNULL_END
#import "UIViewController+Analytics.h"
#import "RSAppAnalyticsManager.h"

@implementation UIViewController (Analytics)


- (void)_RSAnalytics_viewWillAppear:(BOOL)animated{
    NSString* pageName = [UIViewController _RS_getViewControllerPageName:self];
    if (pageName && pageName.length > 0) {
        [RSAppAnalyticsManager RS_beginLogPageView:pageName];
    }
    [self _RSAnalytics_viewWillAppear:animated];
}

- (void)_RSAnalytics_viewWillDisappear:(BOOL)animated{
    NSString* pageName = [UIViewController _RS_getViewControllerPageName:self];
    if (pageName && pageName.length > 0) {
        [RSAppAnalyticsManager RS_endLogPageView:pageName];
    }
    [self _RSAnalytics_viewWillDisappear:animated];
}

+ (NSString*)_RS_getViewControllerPageName:(id)vcController{
    UIViewController* controller = (UIViewController*)vcController;
    NSString* pageName = controller.title;
    return pageName;
}
@end

以下是对UIButton、UIView相关事件统计封装

UIButton

#import <UIKit/UIKit.h>
#import "UIView+RSAnalytics.h"

/**
 UIButton事件计数统计分类
 */
@interface UIButton (RSAnalytics)

/**
 为UIButton添加事件,自动添加计数统计功能。

 @param target 目标(响应者)
 @param action 事件
 @param controlEvents 事件类型
 @param eventId 统计事件id
 @param lable 统计事件label
 */
- (void)RS_addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents annlyticEventId:(NSString*)eventId annlyticLable:(NSString*)lable;
@end
#import "UIButton+RSAnalytics.h"
#import <objc/runtime.h>
#import "RSAppAnalyticsManager.h"


static NSString* const RS_eventId;
static NSString* const RS_lable;

static const char* kSUBCLASS_BUTTON_IVAR_NAME = "_rsAnalytics_Target_Button";

@implementation UIButton (RSAnalytics)

- (void)RS_addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents annlyticEventId:(NSString*)eventId annlyticLable:(NSString*)lable{
    if (![target respondsToSelector:action])
        return;
    
    objc_setAssociatedObject(self, &RS_eventId, eventId, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &RS_lable, lable, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    objc_setAssociatedObject(target, kSUBCLASS_BUTTON_IVAR_NAME, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSString* selName = [NSStringFromSelector(action) rangeOfString:@":"].location == NSNotFound ? NSStringFromSelector(@selector(RS_analyticsEvent_)) : NSStringFromSelector(@selector(RS_analyticsEvent_:));
    const char* types = [NSStringFromSelector(action) rangeOfString:@":"].location == NSNotFound ? "v@:" : "v@:@";
    SEL sel = NSSelectorFromString(selName);
    IMP imp = class_getMethodImplementation(self.class, sel);
    if (class_addMethod([target class], sel, imp, types)) {
        Method actionMethod = class_getInstanceMethod([target class], action);
        Method newMethod = class_getInstanceMethod([target class], sel);
        method_exchangeImplementations(actionMethod, newMethod);
    }
    [self addTarget:target action:action forControlEvents:controlEvents];
}

- (void)RS_analyticsEvent_:(UIButton*)eventView{
    [RSAppAnalyticsManager RS_event:eventView.eventId label:eventView.lable];
    [self RS_analyticsEvent_:eventView];
}

- (void)RS_analyticsEvent_{
    UIButton* eventView = objc_getAssociatedObject(self, kSUBCLASS_BUTTON_IVAR_NAME);
    [RSAppAnalyticsManager RS_event:eventView.eventId label:eventView.lable];
    [self RS_analyticsEvent_];
}

#pragma mark  - getter
- (NSString *)eventId{
    return objc_getAssociatedObject(self, &RS_eventId);
}

- (NSString *)lable{
    return objc_getAssociatedObject(self, &RS_lable);
}
@end

UIView点击事件

#import <UIKit/UIKit.h>


/**
 视图添加点击事件分类(同时添加事件计数统计)
 */
@interface UIView (RSAnalytics)

/**
 为视图添加点击事件;自动添加事件计数统计功能

 @param target 目标(响应者)
 @param action 事件
 @param eventId 统计事件id
 @param lable 统计事件label
 */
- (void)RS_addTarget:(id)target forTouchUpInsaidAction:(SEL)action annlyticEventId:(NSString*)eventId annlyticLable:(NSString*)lable;
@end
#import "UIView+RSAnalytics.h"
#import <objc/message.h>
#import "RSAppAnalyticsManager.h"

static NSString* const RS_eventId;
static NSString* const RS_lable;


static const char* kSUBCLASS_VIEW_IVAR_NAME = "_rsAnalytics_Target_View";
@implementation UIView (RSAnalytics)

- (void)RS_addTarget:(id)target forTouchUpInsaidAction:(SEL)action annlyticEventId:(NSString*)eventId annlyticLable:(NSString*)lable{
    if (![target respondsToSelector:action])
        return;
    
    if (!self.userInteractionEnabled)
        self.userInteractionEnabled = YES;
    
    objc_setAssociatedObject(self, &RS_eventId, eventId, OBJC_ASSOCIATION_COPY_NONATOMIC);
    objc_setAssociatedObject(self, &RS_lable, lable, OBJC_ASSOCIATION_COPY_NONATOMIC);
    
    objc_setAssociatedObject(target, kSUBCLASS_VIEW_IVAR_NAME, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSString* selName = [NSStringFromSelector(action) rangeOfString:@":"].location == NSNotFound ? NSStringFromSelector(@selector(RS_analyticsEvent_)) : NSStringFromSelector(@selector(RS_analyticsEvent_:));
    const char* types = [NSStringFromSelector(action) rangeOfString:@":"].location == NSNotFound ? "v@:" : "v@:@";
    SEL sel = NSSelectorFromString(selName);
    IMP imp = class_getMethodImplementation(self.class, sel);
    if (class_addMethod([target class], sel, imp, types)) {
        Method actionMethod = class_getInstanceMethod([target class], action);
        Method newMethod = class_getInstanceMethod([target class], sel);
        method_exchangeImplementations(actionMethod, newMethod);
    }
    UITapGestureRecognizer* tapGest = [[UITapGestureRecognizer alloc] initWithTarget:target action:action];
    [self addGestureRecognizer:tapGest];
}

- (void)RS_analyticsEvent_:(UIGestureRecognizer*)ges{
    [RSAppAnalyticsManager RS_event:ges.view.eventId label:ges.view.lable];
    [self RS_analyticsEvent_:ges];
}

- (void)RS_analyticsEvent_{
    UIButton* eventView = objc_getAssociatedObject(self, kSUBCLASS_VIEW_IVAR_NAME);
    [RSAppAnalyticsManager RS_event:eventView.eventId label:eventView.lable];
    [self RS_analyticsEvent_];
}

#pragma mark  - getter setter
- (NSString *)eventId{
    return objc_getAssociatedObject(self, &RS_eventId);
}

- (NSString *)lable{
    return objc_getAssociatedObject(self, &RS_lable);
}
@end

初版,没有深入测试过,如有问题或者更好的实现方法可在评论中留言

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容