埋点可以解决两大类问题: 一是了解用户使用App的行为, 二是降低分析线上问题的难度
常见的埋点方式: 代码埋点、可视化埋点、无埋点
代码埋点: 通过手写代码, 精确的在需要埋点的代码处加上埋点的代码, 存在开发工作量大, 后期难以维护的问题
可视化埋点: 增加修改的工作可视化了, 提升体验
无埋点: 埋点代码不会出现在业务代码中, 容易管理和维护, 缺点在于, 埋点成本高, 后期解析复杂, 优点是节省大量的开发和维护成本
运行时方法替换方式进行埋点
常见三种埋点: 页面进入次数、页面停留时间、点击事件
利用运行时method_exchangeImplementations接口将方法的实现进行了交换, 原方法调用时就会被hook住, 从而去执行指定的方法
#import "SMHook.h"
#import <objc/runtime.h>
@implementation SMHook
+ (void)hookClass:(Class)classObject fromSelector:(SEL)fromSelector toSelector:(SEL)toSe
Class class = classObject;
// 得到被替换类的实例方法
Method fromMethod = class_getInstanceMethod(class, fromSelector);
// 得到替换类的实例方法
Method toMethod = class_getInstanceMethod(class, toSelector);
// class_addMethod 返回成功表示被替换的方法没实现,然后会通过 class_addMethod 方法先实现
if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_g
// 进行方法的替换
class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), met
} else {
// 交换 IMP 指针
method_exchangeImplementations(fromMethod, toMethod);
}
}
@end
页面进入次数、页面停留时间都需要对UIViewController生命周期进行埋点, 可以创建一个UIViewController的Catagory
@implementation UIViewController (logger)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通过 @selector 获得被替换和替换方法的 SEL,作为 SMHook:hookClass:fromeSelector:toSelector 的参数传入
SEL fromSelectorAppear = @selector(viewWillAppear:);
SEL toSelectorAppear = @selector(hook_viewWillAppear:);
[SMHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
SEL fromSelectorDisappear = @selector(viewWillDisappear:);
SEL toSelectorDisappear = @selector(hook_viewWillDisappear:);
[SMHook hookClass:self fromSelector:fromSelectorDisappear toSelector:toSelectorDisappear];
});
}
- (void)hook_viewWillAppear:(BOOL)animated {
// 先执行插入代码,再执行原 viewWillAppear 方法
[self insertToViewWillAppear];
[self hook_viewWillAppear:animated];
}
- (void)hook_viewWillDisappear:(BOOL)animated {
// 执行插入代码,再执行原 viewWillDisappear 方法
[self insertToViewWillDisappear];
[self hook_viewWillDisappear:animated];
}
- (void)insertToViewWillAppear {
// 在 ViewWillAppear 时进行日志的埋点
[[[[SMLogger create]
message:[NSString stringWithFormat:@"%@ Appear",NSStringFromClass([self class])]]
classify:ProjectClassifyOperation]
save];
}
- (void)insertToViewWillDisappear {
// 在 ViewWillDisappear 时进行日志的埋点
[[[[SMLogger create]
message:[NSString stringWithFormat:@"%@ Disappear",NSStringFromClass([self class])]]
classify:ProjectClassifyOperation]
save];
}
@end
对于点击事件, 可以通过运行时方法替换的方式进行无侵入埋点, 找到点击事件的方法sendAction:to:forEvent:, 然后在+load()方法使用SMHook替换
UIButton在一个视图类中可能有多个不同的继承类, 相同UIButton的子类在不同视图类的埋点也要区别开, 需要通过“action选择器名 NSStringFromSelector(action)”+“视图类名NSStringFromClass([target class])”组合成一个唯一的标识, 进行埋点
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通过 @selector 获得被替换和替换方法的 SEL,作为 SMHook:hookClass:fromeSelector:toSelector 的参数传入
SEL fromSelector = @selector(sendAction:to:forEvent:);
SEL toSelector = @selector(hook_sendAction:to:forEvent:);
[SMHook hookClass:self fromSelector:fromSelector toSelector:toSelector];
});
}
- (void)hook_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[self insertToSendAction:action to:target forEvent:event];
[self hook_sendAction:action to:target forEvent:event];
}
- (void)insertToSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
// 日志记录
if ([[[event allTouches] anyObject] phase] == UITouchPhaseEnded) {
NSString *actionString = NSStringFromSelector(action);
NSString *targetName = NSStringFromClass([target class]);
[[[SMLogger create] message:[NSString stringWithFormat:@"%@ %@",targetName,actionString]] save];
}
}