解決什么問題?
- 了解用戶使用App的行為
- 降低分析線上問題的難度
常見埋點方式?
- 代碼埋點
- 可視化埋點
- 無埋點
三者對比
| 類型 | 實現(xiàn)形式 | 優(yōu)點 | 缺點 | 是否侵入 |
|---|---|---|---|---|
| 代碼埋點 | 在需要埋點的代碼上增加埋點代碼 | 方便記錄當前環(huán)境變量值,方便調(diào)試,跟蹤埋點內(nèi)容 | 開發(fā)工作量大,代碼到處散布,難以維護 | 是 |
| 可視化埋點 | 埋點增加和修改的工作可視化 | 提升了增加和修改埋點的體驗 | N/A | 否 |
| 無埋點 | 全埋點 | 埋點代碼不會出現(xiàn)在業(yè)務代碼中,容易管理和維護 | 埋點成本高,后期解析負責,view_path的不確定性 | 否 |
小結:使用無侵入埋點方案,既可以做到埋點被統(tǒng)一維護,又可以實現(xiàn)和工程代碼的解耦
無侵入埋點方案實現(xiàn)?
運行時方法替換方式進行埋點?
常見三者埋點
- 頁面進入次數(shù)
- 頁面停留時間
- 點擊事件
思路:對于上述三種常見的埋點,可以通過運行時方法替換技術來插入埋點代碼。
實現(xiàn):先寫一個運行時方法替換的類SMHook,加上替換的方法hookClass:fromSelector:toSelector
#import "JZYHook.h"
#import <objc/runtime.h>
@implementation JZYHook
+ (void)hookClass:(Class)classObject fromSelector:(SEL)fromSelector toSelector:(SEL)toSelector {
Class class = classObject;
// 得到被替換類的實例方法
Method fromMethod = class_getInstanceMethod(class, fromSelector);
// 得到替換類的實例方法
Method toMethod = class_getInstanceMethod(class, toSelector);
// class_addMethod 返回成功表示被替換的方法沒實現(xiàn),然后會通過 class_addMethod 方法先實現(xiàn),然后進行替換;返回失敗則表示被替換方法已存在,可以直接進行 IMP 指針交換
if (class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
// 進行方法替換
class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
}
else {
// 交換 IMP 指針
method_exchangeImplementations(fromMethod, toMethod);
}
}
@end
頁面進入次數(shù)、頁面停留時間都需要對UIViewController生命周期進行埋點
#import "JZYHook.h"
#import "JZYLogger.h"
@implementation UIViewController (JZYLogger)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通過 @selector 獲得被替換和替換方法的 SEL,作為 JZYHook: hookClass:fromSelector:toSelector 的參數(shù)傳入
SEL fromSelectorAppear = @selector(viewWillAppear:);
SEL toSelectorAppear = @selector(hook_viewWillAppear:);
[JZYHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
SEL fromSelectorDisappear = @selector(viewWillDisappear:);
SEL toSelectorDisappear = @selector(hook_viewWillDisappear:);
[JZYHook 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];
}
- (void)insertToViewWillAppear {
// 在viewWillAppear 時進行日志的埋點
[[[JZYLogger create] message:[NSString stringWithFormat:@"%@ Appear", NSStringFromClass([self class])] classify:@"ProjectClassifyOperation"] save];
}
- (void)insertToViewWillDisappear {
// 在viewWillDisappear 時進行日志的埋點
[[[JZYLogger create] message:[NSString stringWithFormat:@"%@ Disappear", NSStringFromClass([self class])] classify:@"ProjectClassifyOperation"] save];
}
@end
點擊事件,通過運行時方法替換的方式進行埋點
#import "JZYHook.h"
#import "JZYLogger.h"
@implementation UIButton (JZYLogger)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 通過 @selector 獲得被替換和替換方法的 SEL,作為 JZYHook: hookClass:fromSelector:toSelector 的參數(shù)傳入
SEL fromSelectorAppear = @selector(sendAction:to:forEvent:);
SEL toSelectorAppear = @selector(hook_sendAction:to:forEvent:);
[JZYHook hookClass:self fromSelector:fromSelectorAppear toSelector:toSelectorAppear];
});
}
- (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]);
[[[JZYLogger create] message:[NSString stringWithFormat:@"%@ %@", targetName, actionString] classify:@"ProjectClassifyOperation"] save];
}
}
@end
如何區(qū)分相同類在不同視圖樹節(jié)點的情況?
通過一個唯一標識來區(qū)分不同的事件。
思路:想辦法找出元素間不相同的因素然后進行組合,最后形成一個能區(qū)別于其他元素的標識來。
比如:
- 視圖層級的路徑+索引
- UITableViewCell 使用 indexPath
- UIAlertController 使用內(nèi)容確定
總結:由于唯一標識難以維護和準確性難以保障的原因,很難被全面采用,一般只用于一些功能和視圖穩(wěn)定的地方,手動侵入式埋點方式依然占據(jù)大部分場景。
推薦使用 Clang AST