美文网首页综合iOS小知识
iOS基于友盟事件统计封装

iOS基于友盟事件统计封装

作者: 小米咸鱼 | 来源:发表于2019-01-29 15:02 被阅读2次

    统计分析管理类,包含对所有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
    
    

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

    相关文章

      网友评论

        本文标题:iOS基于友盟事件统计封装

        本文链接:https://www.haomeiwen.com/subject/xkyxsqtx.html