埋點可以解決兩大類問題: 一是了解用戶使用App的行為, 二是降低分析線上問題的難度
常見的埋點方式: 代碼埋點、可視化埋點、無埋點
代碼埋點: 通過手寫代碼, 精確的在需要埋點的代碼處加上埋點的代碼, 存在開發(fā)工作量大, 后期難以維護的問題
可視化埋點: 增加修改的工作可視化了, 提升體驗
無埋點: 埋點代碼不會出現(xiàn)在業(yè)務代碼中, 容易管理和維護, 缺點在于, 埋點成本高, 后期解析復雜, 優(yōu)點是節(jié)省大量的開發(fā)和維護成本
運行時方法替換方式進行埋點
常見三種埋點: 頁面進入次數(shù)、頁面停留時間、點擊事件
利用運行時method_exchangeImplementations接口將方法的實現(xiàn)進行了交換, 原方法調用時就會被hook住, 從而去執(zhí)行指定的方法
#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 返回成功表示被替換的方法沒實現(xiàn),然后會通過 class_addMethod 方法先實現(xiàn)
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
頁面進入次數(shù)、頁面停留時間都需要對UIViewController生命周期進行埋點, 可以創(chuàng)建一個UIViewController的Catagory
@implementation UIViewController (logger)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通過 @selector 獲得被替換和替換方法的 SEL,作為 SMHook:hookClass:fromeSelector:toSelector 的參數(shù)傳入
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 {
// 先執(zhí)行插入代碼,再執(zhí)行原 viewWillAppear 方法
[self insertToViewWillAppear];
[self hook_viewWillAppear:animated];
}
- (void)hook_viewWillDisappear:(BOOL)animated {
// 執(zhí)行插入代碼,再執(zhí)行原 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的子類在不同視圖類的埋點也要區(qū)別開, 需要通過“action選擇器名 NSStringFromSelector(action)”+“視圖類名NSStringFromClass([target class])”組合成一個唯一的標識, 進行埋點
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通過 @selector 獲得被替換和替換方法的 SEL,作為 SMHook:hookClass:fromeSelector:toSelector 的參數(shù)傳入
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];
}
}