一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第26天,点击查看活动详情。
前言
在$AppClick事件采集中,还有两个比较特殊 的控件。
- UITableView
- UICollectionView
这两个控件的点击事件,一般指的是点击UITableViewCell和UICollectionViewCell。而UITableViewCell和UICollectionViewCell都是直接继承自UIView类,而不是UIControl类,因此,我们之前实现$AppClick事件全埋点的两个方案均不适用于UITableView和UICollectionView控件。
关于实现UITableView和UICollectionView控 件$AppClick事件的全埋点,常见的方案有三种。
- 方法交换
- 动态子类
- 消息转发
这三种方案各有优缺点。下面,我们以 UITableView控件为例,分别介绍如何使用这三种 方案实现$AppClick事件的全埋点。
支持UITableView控件
方案一:方法交换
众所周知,如果需要处理UITableView的点击操作,需要先设置 UITableView的delegate属性,并实现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