iOS 全埋点-控件点击事件3

122 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情

方案二

描述

当一个视图被添加到父视图上时,系统会自 动调用-didMoveToSuperview方法。因此,我们可 以通过Method Swizzling交换UIView- didMoveToSuperview方法,然后在交换方法里给 控件添加一组UIControlEventTouchDown类型的 Target-Action,并在Action里触发$AppClick事 件,从而实现$AppClick事件全埋点,这就是方案二的实现原理。

代码实现

新建一个UIControl的分类

UIControl+CountData.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIControl (CountData)

@end

NS_ASSUME_NONNULL_END

UIControl+CountData.m

+ (void)load {
    
    [UIControl sensorsdata_swizzleMethod:@selector(didMoveToSuperview) withMethod:@selector(CountData_didMoveToSuperview)];
}

- (void)CountData_didMoveToSuperview {
    
    //调用前交换原始方法
    [self CountData_didMoveToSuperview];
    [self addTarget:self action:@selector(CountData_touchDownAction:withEvent:) forControlEvents:UIControlEventTouchDown];

}

-(void)CountData_touchDownAction:(UIControl *)sender withEvent:(UIEvent *)event {
    if ([self CountData_isAddMultipleTargetActionsWithDefaultEvent:UIControlEventTouchDown]) {
        //触发$AppClick事件
        UIView *view = (UIView *)sender;
        NSMutableDictionary *prams = [[NSMutableDictionary alloc]init];
        //获取控件类型
        prams[@"$elementtype"] = view.elementType;
        //获取控件的内容
        prams[@"element_content"] = view.elementContent;
        //获取所属的页面
        UIViewController *vc = view.myViewController;
        prams[@"element_screen"] = NSStringFromClass(vc.class);
          
        [[SensorsAnalyticsSDK sharedInstance]track:@"appclick" properties:prams];
    }
}

注意点UIControl类中其实并没有实现-didMoveToSuperview方法,这个方法是 从它的父类UIView继承而来的。因此,我们实际上交换的是UIView中的- didMoveToSuperview方法。当UIView对象调用-didMoveToSuperview方法时,其实调用的是在UIControl+CountData.m中实现的- CountData_didMoveToSuperview方法。但是,UIView对象或者除了 UIControl类的其他UIView子类的对象,在执行-CountData_didMoveToSuperview方法时,并没有实现-CountData_didMoveToSuperview方法,因此,程序会出现 找不到方法而崩溃的情况。

针对这个问题,我们需要修改NSObject+SASwizzler.m文件中的 +sensorsdata_swizzleMethod:withMethod:类方法,即将其修改为:在方法交换之前,先在当前类中添加需要交换的方法,并在添加成功之后获取新的方法指针。

+ (BOOL)sensorsdata_swizzleMethod:(SEL)originalSEL withMethod:(SEL)alternateSEL {
   
    //获取原始的方法
    Method originalMethod = class_getInstanceMethod(self, originalSEL);
    if (!originalMethod) {
        return NO;
    }
    //获取将要交换的方法
    Method alternateMethod = class_getInstanceMethod(self, alternateSEL);
    if (!alternateMethod) {
        return NO;
    }
    
    //获取originalSel方法实现
    IMP originalIMP = method_getImplementation(originalMethod);
    //获取originalSEL方法的类型
    const char *originalMethodType = method_getTypeEncoding(originalMethod);
    //往类中添加originalSEL方法,如果已经存在,则添加失败,并返回NO
    if (class_addMethod(self, originalSEL, originalIMP, originalMethodType)) {
        //如果添加成功,重新获取originalSEL实例方法
        originalMethod = class_getInstanceMethod(self, originalSEL);
    }

    //获取alternateIMP方法实现
    IMP alternateIMP = method_getImplementation(alternateMethod);
    //获取alternateSEL方法的类型
    const char *alternateMethodType = method_getTypeEncoding(alternateMethod);
    //往类中添加alternateSEL方法,如果已经存在,则添加失败,并返回NO
    if (class_addMethod(self, alternateSEL, alternateIMP, alternateMethodType)) {
        //如果添加成功,重新获取alternateSEL实例方法
        alternateMethod = class_getInstanceMethod(self, alternateSEL);
    }

    //交互两个方法的实现
    method_exchangeImplementations(originalMethod, alternateMethod);  
    //返回yes,方法交换成功
    return YES;
}