技术方案实现的背景:
因为在开发项目的时候,埋点都是手动埋的,每次业务需求的改变都要到处埋点,这就免不了会遗漏埋点。每次在发版之前,大数据的人都会跑过来说某个点没有埋,哎,不仅开发效率变低,而且手动埋点真的好痛苦。在老大的提议下,决定学习一下无埋点技术,来解决我们现在的埋点的现状。经过几番查找资料,大致实施方案是采用运行时的方法交换来进行方法的替换,在需要埋点的地方先埋入点,然后再执行原方法,使得埋点从业务中抽离出来。
技术方案:(现在技术方案只是局限于不与业务逻辑挂钩的埋点,与业务挂钩的埋点还没有想到好的解决方案)
在项目中需要埋入的点大约分为两大类:
第一种:click事件
click事件中大致又分为button的点击事件,手势的点击事件,还有例如tableView的代理点击事件。首先对于button的点击事件,写一个button的分类,在分类中重写load方法,在load方法中进行方法的替换,然后替换button的点击事件-(void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents ,在替换之后的方法中进行埋点的处理,然后再调用原来的点击事件的方法。这样悄悄地埋入了埋点,又不改变之前代码的逻辑。因为UIButton是继承自UIControl的,所以也可以写UIControl的分类,凡是继承自UIControl的控件的响应事件最后都会走-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event此方法,可以将此方法进行替换。但是采用这种给控件写分类的模式不是太喜欢,我需要给项目中所有涉及到click事件的控件写分类,例如button,得写button的分类进行事件的替换,tableView的代理方法的替换得写tabView的分类,碍于这种繁琐,我的解决方案是写UIViewController的分类,给UIViewController中的ViewWillAppear方法进行替换,下面就是重点讲解方法的替换的实现啦,好啦,上代码来解释吧!
import "UIViewController+statistic.h"
import "LFDZHSwizzleTool.h"
import "LFDStatistic.h"
@implementation
UIViewController (statistic)
+ (void)load{
static dispatch_once_t dispatch_once_token; dispatch_once(&dispatch_once_token, ^{
SEL originlSelector = @selector(viewDidAppear:);
SEL newSelector = @selector(swizzleviewWillAppear:);
//此处是我封装的方法交换的方法
[LFDZHSwizzleTool zhSwizzleWithOrigionClass:[self class] originalSelector:originlSelector swizzleClass:[self class] swizzleSelector:newSelector]; });
}
- (void)swizzleviewWillAppear:(BOOL)animated{
//加载plist
NSString *pathString = [[NSBundle mainBundle]pathForResource:@"buttonStatistics" ofType:@"plist"];
//获取加载plsit的字典
NSDictionary *plistDic = [NSDictionary dictionaryWithContentsOfFile:pathString];
//获取当前类的方法列表 NSDictionary *classMthodDic = plistDic[NSStringFromClass([self class])];
//如果没有获取就返回 if (classMthodDic == nil) return; // 该类的方法不需要hook
//获取方法的所有的key值,放到数组当中 NSArray *methodNameArray = [classMthodDic allKeys];
//遍历所有的方法
for (NSString *methodString in methodNameArray) {
__block SEL originalSEL = NSSelectorFromString(methodString);
// 这里需要判断是否是UIControl的事件,以防止两个地方同时hook,造成重复上传
BOOL isResponseOriginSEL = [self respondsToSelector:originalSEL];
//如果该类中有此方法那么就替换该类中的方法
if (isResponseOriginSEL) {
//使用aspect替换类中的方法 ,aspect是一个封装好的用来交换方法的第三方的框架,可以去gitHub上去下载
[[self class] aspect_hookSelector:originalSEL withOptions:AspectPositionAfter usingBlock:^(idinfo){
NSString *selectorName = NSStringFromSelector(originalSEL);
NSString *pathString = [[NSBundle mainBundle]pathForResource:@"buttonStatistics" ofType:@"plist"];
NSDictionary *plistDic = [NSDictionary dictionaryWithContentsOfFile:pathString];
// 2.获取某个类的方法列表
NSDictionary *classMthodDic = plistDic[NSStringFromClass([self class])];
// 3.获取其中某个方法的参数列表
NSDictionary *parameterDic = classMthodDic[selectorName];
NSString *eventID = parameterDic[@"eventID"];
NSString *tarPageID = parameterDic[@"tarPageID"];
//在此处上报需要埋入的点
[LFDStatistic lfd_clickName:eventID tar_page_name:tarPageID params:nil];
}
error:nil];
}
}
[self swizzleviewWillAppear:animated];
}
plist表:
主要实现逻辑:给每个控制器的ViewWillAppear方法进行替换,在类中检索是否有plist表中需要埋入的点,如果有,就替换方法,在替换的方法中进行埋点。
第二种:expose事件
项目中因为所有的控制器都继承自一个baseVc,所以统一在baseVc的viewWillAppear方法中埋入expose事件。当然也可以在交换的方法中进行埋入。
以上只是解决了简单的不涉及业务的click事件,在接下来的日子里会继续跟踪更好的方法来解决这个问题。以上文章的实录要感谢来自上的简友。
埋点资料:https://github.com/Vienta/VIAnalyticsKit/blob/master/VIAnalyticsKit/Class/VIAnalyticsAOP.m