iOS 埋点

2022-08-19  本文已影响0人  cocoaCoffee

所谓埋点就是App端采集用户某些事件的数据,提交到服务器进行分析。那某些事件是指哪些事件呢?譬如App的启动和退出、浏览某个页面、一些的点击事件等等。项目中需要埋点一般用全埋点,何为全埋点?即是指无需应用程序开发工程师写代码或者只写少量的代码,即可预先自动收集用户的所有或者绝大部分的行为数据,然后再根据实际的业务分析需求从中筛选出所需行为数据并进行分析。下面是我项目中做的几种事件埋点。

一、App的启动和退出

直接在AppDelegate实现对应的方法

回调方法 本地通知 通知时机
- applicationDidBecomeActive: UIApplicationDidBecomeActiveNotification 从后台已经进入前台
- applicationDidEnterBackground: UIApplicationDidEnterBackgroundNotification 已经进入后台
- application:didFinishLaunchingWithOptions: UIApplicationDidFinishLaunchingNotification 进入程序
- applicationWillTerminate: UIApplicationWillTerminateNotification 将退出程序
AppDelegate.m
- (void)applicationWillTerminate:(UIApplication *)application
{
    // 将要退出程序,调用对应接口
}

可以创建一个埋点管理类BuryPointManager,用来提交数据给服务器

AppDelegate.m
- (void)applicationWillTerminate:(UIApplication *)application
{
    // 将要退出程序,调用对应接口
    [[BuryPointManager sharedInstance] 方法];
}

如果有SceneDelegate,applicationDidBecomeActive:applicationDidEnterBackground:等方法在AppDelegate实现不了(application:didFinishLaunchingWithOptions:applicationWillTerminate:还是在AppDelegate实现),要去SceneDelegate实现,applicationDidBecomeActive:applicationDidEnterBackground:对应SceneDelegate的sceneDidBecomeActive:sceneDidEnterBackground:

SceneDelegate.m
- (void)sceneDidBecomeActive:(UIScene *)scene {
     [[BuryPointManager sharedInstance] 方法];
}

二、页面浏览事件

页面浏览事件分浏览某个页面和统计某个页面的浏览时长

方法一:用视图控制器基类埋点

一般项目创建都会创建一个UIViewController的子类当页面的基类,如叫BaseViewController,那么可以到这个类里面进行埋点

BaseViewController.m
- (void)viewDidLoad{
    [super viewDidLoad];

    // 如果是ViewController这个控制器就提交服务器
    if ([self isKindOfClass:[ViewController class]]) {
         [[BuryPointManager sharedInstance] 方法];
    }
}
BaseViewController.m

// 不能用viewWillAppear和viewDidDisappear或者viewDidAppear和viewWillDisappear统计,如viewWillAppear和viewDidDisappear,因为下一个页面的viewWillAppear会比上一个页面的viewDidDisappear触发的快,因此[BuryPointManager sharedInstance].time会重置
- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear: animated];
    // 可以在管理类里面定义个属性记录当前时间
    [BuryPointManager sharedInstance].time = CFAbsoluteTimeGetCurrent();
}

- (void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear: animated];

    // 根据页面出现和消失计算浏览时间
    NSTimeInterval time = CFAbsoluteTimeGetCurrent() -  [BuryPointManager sharedInstance].time;
    // 这里可以根据是哪个控制器再提交服务器, 方法里面带上时间和需要计算时长的页面控制器
    if ([self isKindOfClass:[ViewController class]]) {
         [[BuryPointManager sharedInstance] 方法];
    } else if () {

    } ...
    // 如果每次判断麻烦,可以在BuryPointManager里面定义一个viewControllers的数组来存需要埋点的控制器
    for (NSString *name in [BuryPointManager sharedInstance].viewControllers) {
        if ([name isEqualToString:NSStringFromClass([self class])]) {
            [[BuryPointManager sharedInstance] 方法];
            break;
        }
    }

}

方法二:使用hook

首先先写一个写个分类实现方法替换

NSObject+LVSwizzle.h

@interface NSObject (LVSwizzle)

/**
 *  @brief  替换方法实现
 *
 *  @param srcSel 替换的方法
 *  @param tarClassName 被替换的方法的类名
 *  @param tarSel 被替换的方法
 */
+ (void)lv_swizzleMethod:(SEL)srcSel tarClass:(NSString *)tarClassName tarSel:(SEL)tarSel;

NSObject+LVSwizzle.m

#import "NSObject+LVSwizzle.h"
#import <objc/runtime.h>

@implementation NSObject (LVSwizzle)

+ (void)lv_swizzleMethod:(SEL)srcSel tarClass:(NSString *)tarClassName tarSel:(SEL)tarSel{
    if (!tarClassName) {
        return;
    }
    Class srcClass = [self class];
    Class tarClass = NSClassFromString(tarClassName);
    [self lv_swizzleMethod:srcClass srcSel:srcSel tarClass:tarClass tarSel:tarSel];
}

+ (void)lv_swizzleMethod:(Class)srcClass srcSel:(SEL)srcSel tarClass:(Class)tarClass tarSel:(SEL)tarSel{
    if (!srcClass) {
        return;
    }
    if (!srcSel) {
        return;
    }
    if (!tarClass) {
        return;
    }
    if (!tarSel) {
        return;
    }
    Method srcMethod = class_getInstanceMethod(srcClass,srcSel);
    Method tarMethod = class_getInstanceMethod(tarClass,tarSel);
    method_exchangeImplementations(srcMethod, tarMethod);
}

@end

创建UIViewController的分类UIViewController+BuryPoint

UIViewController+BuryPoint.m

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(viewDidLoad);
        SEL swizzledSelector = @selector(lv_viewDidLoad);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
        BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) { // iOS 13.6崩溃找不到方法,最好判断下
            class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)lv_viewDidLoad
{
    [self lv_viewDidLoad];
    
    // 如果是ViewController这个控制器就提交服务器
    if ([self isKindOfClass:[ViewController class]]) {
         [[BuryPointManager sharedInstance] 方法];
    }
}
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(viewDidAppear:);
        SEL swizzledSelector = @selector(lv_viewDidAppear:);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
        BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
        SEL originalSelector1 = @selector(viewDidDisappear:);
        SEL swizzledSelector1 = @selector(lv_viewDidDisappear:);
        Method originalMethod1 = class_getInstanceMethod([self class], originalSelector1);
        Method swizzledMethod1 = class_getInstanceMethod([self class], swizzledSelector1);
        BOOL success1 = class_addMethod([self class], originalSelector1, method_getImplementation(swizzledMethod1), method_getTypeEncoding(swizzledMethod1));
        if (success1) {
            class_replaceMethod([self class], swizzledSelector1, method_getImplementation(originalMethod1), method_getTypeEncoding(originalMethod1));
        } else {
            method_exchangeImplementations(originalMethod1, swizzledMethod1);
        }
    });
}

- (void)lv_viewDidAppear:(BOOL)animated
{
    [self lv_viewDidAppear:animated];
    
    // 可以在管理类里面定义个属性记录当前时间
    [BuryPointManager sharedInstance].time = CFAbsoluteTimeGetCurrent();
}
    
- (void)lv_viewDidDisappear:(BOOL)animated
{
    [self lv_viewDidDisappear:animated];

    // 根据页面出现和消失计算浏览时间
    NSTimeInterval time = CFAbsoluteTimeGetCurrent() -  [BuryPointManager sharedInstance].time;
    // 这里可以根据是哪个控制器再提交服务器, 方法里面带上时间和需要计算时长的页面控制器
    if ([self isKindOfClass:[ViewController class]]) {
         [[BuryPointManager sharedInstance] 方法];
    } else if () {

    } ...
    // 如果每次判断麻烦,可以在BuryPointManager里面定义一个viewControllers的数组来存需要埋点的控制器
    for (NSString *name in [BuryPointManager sharedInstance].viewControllers) {
        if ([name isEqualToString:NSStringFromClass([self class])]) {
            [[BuryPointManager sharedInstance] 方法];
            break;
        }
    }
}

三、点击事件

点击事件分为UIControl(UIButton)的点击事件、手势点击事件、UITableView和UICollectionView的点击事件

UIControl+BuryPoint.m
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{    
        SEL originalSelector = @selector(sendAction:to:forEvent:);
        SEL swizzledSelector = @selector(lv_sendAction:to:forEvent:);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
        BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
- (void)lv_sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event {
    [self lv_sendAction:action to:target forEvent:event];

    // 要区分具体控件,可以用tag值来区分,最好看接口需要用什么值来标识,可以把这个值设为控件的tag值,如果标识是字母可以先转成16进制再转成10进制
    [[BuryPointManager sharedInstance] 方法];
}

UITapGestureRecognizer+BuryPoint.m

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(initWithTarget:action:);
        SEL swizzledSelector = @selector(countData_initWithTarget:action:);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
        BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
        SEL originalSelector1 = @selector(addTarget:action:);
        SEL swizzledSelector1 = @selector(countData_addTarget:action:);
        Method originalMethod1 = class_getInstanceMethod([self class], originalSelector1);
        Method swizzledMethod1 = class_getInstanceMethod([self class], swizzledSelector1);
        BOOL success1 = class_addMethod([self class], originalSelector1, method_getImplementation(swizzledMethod1), method_getTypeEncoding(swizzledMethod1));
        if (success1) {
            class_replaceMethod([self class], swizzledSelector1, method_getImplementation(originalMethod1), method_getTypeEncoding(originalMethod1));
        } else {
            method_exchangeImplementations(originalMethod1, swizzledMethod1);
        }
    });
}

- (instancetype)countData_initWithTarget:(id)target action:(SEL)action {
    [self countData_initWithTarget:target action:action];
    [self addTarget:target action:action];
    return self;
}

- (void)countData_addTarget:(id)target action:(SEL)action {
    [self countData_addTarget:target action:action];
    [self countData_addTarget:self action:@selector(countData_trackTapGestureAction:)];
}

- (void)countData_trackTapGestureAction:(UITapGestureRecognizer *)sender {
    UIView *tapView =  sender.view;

    [[BuryPointManager sharedInstance] 方法];
}


UITableView+BuryPoint.m

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL originalSelector = @selector(setDelegate:);
        SEL swizzledSelector = @selector(hook_setDelegate:);
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
        BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (success) {
            class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)hook_setDelegate:(id<SDCycleScrollViewDelegate>)delegate {
    SEL originalSelector = @selector(tableView:didSelectRowAtIndexPath:);
    SEL swizzledSelector = @selector(lv_tableView:didSelectRowAtIndexPath:);
    Method originalMethod_delegate = class_getInstanceMethod([delegate class], originalSelector);
    Method originalMethod_self = class_getInstanceMethod([self class], originalSelector);
    Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
    //若未实现代理方法,则先添加代理方法
    BOOL didAddOriginalMethod = class_addMethod([delegate class], originalSelector, method_getImplementation(originalMethod_self), method_getTypeEncoding(originalMethod_self));
    if (!didAddOriginalMethod) {
        //已经实现代理方法,则先将hook的方法添加到delegate class中,然后再交换Imp指针
        BOOL didAddSwizzledMethod = class_addMethod([delegate class], swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (didAddSwizzledMethod) {
            Method newMethod = class_getInstanceMethod([delegate class], swizzledSelector);
            method_exchangeImplementations(originalMethod_delegate, newMethod);
        } else {
            method_exchangeImplementations(originalMethod_delegate, swizzledMethod);
        }
    }
    [self hook_setDelegate:delegate];
}

- (void)lv_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self lv_tableView:tableView didSelectRowAtIndexPath:indexPath];
    
    [[BuryPointManager sharedInstance] 方法];
}

上一篇 下一篇

猜你喜欢

热点阅读