iOS 全埋点-UITaleView和UICollectionView的点击事件

143 阅读2分钟

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

前言

$AppClick事件采集中,还有两个比较特殊 的控件。

  • UITableView
  • UICollectionView
    这两个控件的点击事件,一般指的是点击 UITableViewCellUICollectionViewCell。而 UITableViewCellUICollectionViewCell都是直接继承自UIView类,而不是UIControl类,因此,我们之前实现$AppClick事件全埋点的两个方案均不适用于UITableView和UICollectionView控件。

关于实现UITableView和UICollectionView控 件$AppClick事件的全埋点,常见的方案有三种。

  1. 方法交换
  2. 动态子类
  3. 消息转发

这三种方案各有优缺点。下面,我们以 UITableView控件为例,分别介绍如何使用这三种 方案实现$AppClick事件的全埋点。

支持UITableView控件

方案一:方法交换

众所周知,如果需要处理UITableView的点击操作,需要先设置 UITableViewdelegate属性,并实现UITableViewDelegate协议的- tableView:didSelectRowAtIndexPath:方法。因此,我们也很容易想到使用 Method Swizzling交换-tableView:didSelectRowAtIndexPath:方法来实现 UITableView控件$AppClick事件的全埋点

初始思路
首先,我们使用Method Swizzling交换UITableView- setDelegate:方法;然后,获取实现UITableViewDelegate协议的delegate对象,在得到delegate对象之后,交换delegate对象的- tableView:didSelectRowAtIndexPath:方法;最后,在交换后的方法中触发 $AppClick事件,从而实现UITableView控件$AppClick事件全埋点。

新建一个UITableView的类别CountData

UItableView+CountData.m

#import "UITableView+CountData.h"
#import "NSObject+Swizzler.h"
#import <objc/message.h>
#import <objc/runtime.h>
#import "SensorsAnalyticsSDK.h"

@implementation UITableView (CountData)

+ (void)load { 
    [UITableView sensorsdata_swizzleMethod:@selector(setDelegate:) withMethod:@selector(CountData_setDelegate:)];
}

/*
 *  UITableView的delegate对象是在程序运行时设置的,其有可能是UItableView对象本身,也有可能是UIviewController或者其他对象。因此需要给delegate对象动态地添加需要交换的方法,然后与原来的tableView:didSelectRowAtIndexPath:方法进行交换。
 */

- (void)CountData_setDelegate:(id<UITableViewDelegate>)delegate {
    [self CountData_setDelegate:delegate];
    [self CountData_swizzleDidSelectRowAtIndexPathMethodWithDelegate:delegate];
}


//添加交换方法
static void CountData_tableViewDidSelectRow(id object,SEL selector,UITableView *tableView,NSIndexPath *indexPath) {
    SEL destinationSelector = NSSelectorFromString(@"CountData_tableView:didSelectRowAtIndexPath:");
    //发送消息,调用原始的tableView:didSelectRowAtIndexPath:方法实现   
    ((void (*)(id,SEL,id,id))objc_msgSend)(object,destinationSelector,tableView,indexPath);   
    [[SensorsAnalyticsSDK sharedInstance]AppClickWithTableView:tableView didSelectRowAtIndexPath:indexPath properties:nil];
}

#pragma mark- 私有方法,负责给delegate对象添加一个方法并进行交换
-(void)CountData_swizzleDidSelectRowAtIndexPathMethodWithDelegate:(id)delegate {
   //获取delegate对象的类
    Class delegateClass = [delegate class];
    NSLog(@"获取当前对象的类型名字为---%@",NSStringFromClass([delegate class]));
    //方法名
    SEL sourceSelector = @selector(tableView:didSelectRowAtIndexPath:);
    //当delegate对象中没有实现方法tableView:didSelectRowAtIndexPath:,直接返回
    if (![delegate respondsToSelector:sourceSelector]) {
        NSLog(@"没有实现tableView:didSelectRowAtIndexPath方法");
        return;
    }
    
    SEL destinationSelector = NSSelectorFromString(@"CountData_tableView:didSelectRowAtIndexPath:");
    //当delegate对象已经存在了CountData_tableView:didSelectRowAtIndexPath:,说明已经交换,可以直接返回
    if ([delegate respondsToSelector:destinationSelector]) {
        return;
    }
    
    Method sourceMethod = class_getInstanceMethod(delegateClass, sourceSelector);
    const char *encoding = method_getTypeEncoding(sourceMethod);
    //当类中已经存在相同的方法时,则会添加方法失败。当时前面已经判断过方法是否存在。因此,此处一定会添加成功
    if (!class_addMethod([delegate class], destinationSelector,(IMP)CountData_tableViewDidSelectRow, encoding)) {
        
        return;
    }
    
    //方法添加之后,进行方法交换
    [delegateClass sensorsdata_swizzleMethod:sourceSelector withMethod:destinationSelector];
}

@end