所謂埋點就是App端采集用戶某些事件的數(shù)據(jù)铃慷,提交到服務器進行分析考抄。那某些事件是指哪些事件呢?譬如App的啟動和退出沐绒、瀏覽某個頁面俩莽、一些的點擊事件等等。項目中需要埋點一般用全埋點洒沦,何為全埋點豹绪?即是指無需應用程序開發(fā)工程師寫代碼或者只寫少量的代碼,即可預先自動收集用戶的所有或者絕大部分的行為數(shù)據(jù),然后再根據(jù)實際的業(yè)務分析需求從中篩選出所需行為數(shù)據(jù)并進行分析瞒津。下面是我項目中做的幾種事件埋點蝉衣。
一、App的啟動和退出
直接在AppDelegate實現(xiàn)對應的方法
回調(diào)方法 | 本地通知 | 通知時機 |
---|---|---|
- applicationDidBecomeActive: | UIApplicationDidBecomeActiveNotification | 從后臺已經(jīng)進入前臺 |
- applicationDidEnterBackground: | UIApplicationDidEnterBackgroundNotification | 已經(jīng)進入后臺 |
- application:didFinishLaunchingWithOptions: | UIApplicationDidFinishLaunchingNotification | 進入程序 |
- applicationWillTerminate: | UIApplicationWillTerminateNotification | 將退出程序 |
AppDelegate.m
- (void)applicationWillTerminate:(UIApplication *)application
{
// 將要退出程序巷蚪,調(diào)用對應接口
}
可以創(chuàng)建一個埋點管理類BuryPointManager病毡,用來提交數(shù)據(jù)給服務器
AppDelegate.m
- (void)applicationWillTerminate:(UIApplication *)application
{
// 將要退出程序,調(diào)用對應接口
[[BuryPointManager sharedInstance] 方法];
}
如果有SceneDelegate屁柏,applicationDidBecomeActive:
啦膜、applicationDidEnterBackground:
等方法在AppDelegate實現(xiàn)不了(application:didFinishLaunchingWithOptions:
、applicationWillTerminate:
還是在AppDelegate實現(xiàn))淌喻,要去SceneDelegate實現(xiàn)僧家,applicationDidBecomeActive:
、applicationDidEnterBackground:
對應SceneDelegate的sceneDidBecomeActive:
裸删、sceneDidEnterBackground:
SceneDelegate.m
- (void)sceneDidBecomeActive:(UIScene *)scene {
[[BuryPointManager sharedInstance] 方法];
}
二八拱、頁面瀏覽事件
頁面瀏覽事件分瀏覽某個頁面和統(tǒng)計某個頁面的瀏覽時長
方法一:用視圖控制器基類埋點
一般項目創(chuàng)建都會創(chuàng)建一個UIViewController的子類當頁面的基類,如叫BaseViewController涯塔,那么可以到這個類里面進行埋點
- 瀏覽某個頁面
BaseViewController.m
- (void)viewDidLoad{
[super viewDidLoad];
// 如果是ViewController這個控制器就提交服務器
if ([self isKindOfClass:[ViewController class]]) {
[[BuryPointManager sharedInstance] 方法];
}
}
- 統(tǒng)計時長
BaseViewController.m
// 不能用viewWillAppear和viewDidDisappear或者viewDidAppear和viewWillDisappear統(tǒng)計肌稻,如viewWillAppear和viewDidDisappear,因為下一個頁面的viewWillAppear會比上一個頁面的viewDidDisappear觸發(fā)的快匕荸,因此[BuryPointManager sharedInstance].time會重置
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear: animated];
// 可以在管理類里面定義個屬性記錄當前時間
[BuryPointManager sharedInstance].time = CFAbsoluteTimeGetCurrent();
}
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear: animated];
// 根據(jù)頁面出現(xiàn)和消失計算瀏覽時間
NSTimeInterval time = CFAbsoluteTimeGetCurrent() - [BuryPointManager sharedInstance].time;
// 這里可以根據(jù)是哪個控制器再提交服務器, 方法里面帶上時間和需要計算時長的頁面控制器
if ([self isKindOfClass:[ViewController class]]) {
[[BuryPointManager sharedInstance] 方法];
} else if () {
} ...
// 如果每次判斷麻煩爹谭,可以在BuryPointManager里面定義一個viewControllers的數(shù)組來存需要埋點的控制器
for (NSString *name in [BuryPointManager sharedInstance].viewControllers) {
if ([name isEqualToString:NSStringFromClass([self class])]) {
[[BuryPointManager sharedInstance] 方法];
break;
}
}
}
方法二:使用hook
首先先寫一個寫個分類實現(xiàn)方法替換
NSObject+LVSwizzle.h
@interface NSObject (LVSwizzle)
/**
* @brief 替換方法實現(xiàn)
*
* @param srcSel 替換的方法
* @param tarClassName 被替換的方法的類名
* @param tarSel 被替換的方法
*/
+ (void)lv_swizzleMethod:(SEL)srcSel tarClass:(NSString *)tarClassName tarSel:(SEL)tarSel;
NSObject+LVSwizzle.m
#import "NSObject+LVSwizzle.h"
#import <objc/runtime.h>
@implementation NSObject (LVSwizzle)
+ (void)lv_swizzleMethod:(SEL)srcSel tarClass:(NSString *)tarClassName tarSel:(SEL)tarSel{
if (!tarClassName) {
return;
}
Class srcClass = [self class];
Class tarClass = NSClassFromString(tarClassName);
[self lv_swizzleMethod:srcClass srcSel:srcSel tarClass:tarClass tarSel:tarSel];
}
+ (void)lv_swizzleMethod:(Class)srcClass srcSel:(SEL)srcSel tarClass:(Class)tarClass tarSel:(SEL)tarSel{
if (!srcClass) {
return;
}
if (!srcSel) {
return;
}
if (!tarClass) {
return;
}
if (!tarSel) {
return;
}
Method srcMethod = class_getInstanceMethod(srcClass,srcSel);
Method tarMethod = class_getInstanceMethod(tarClass,tarSel);
method_exchangeImplementations(srcMethod, tarMethod);
}
@end
創(chuàng)建UIViewController的分類UIViewController+BuryPoint
- 瀏覽某個頁面就hook控制器的viewDidLoad方法
UIViewController+BuryPoint.m
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(lv_viewDidLoad);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) { // iOS 13.6崩潰找不到方法,最好判斷下
class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)lv_viewDidLoad
{
[self lv_viewDidLoad];
// 如果是ViewController這個控制器就提交服務器
if ([self isKindOfClass:[ViewController class]]) {
[[BuryPointManager sharedInstance] 方法];
}
}
- 統(tǒng)計時長就hook viewDidAppear和viewDidDisappear
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(viewDidAppear:);
SEL swizzledSelector = @selector(lv_viewDidAppear:);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
SEL originalSelector1 = @selector(viewDidDisappear:);
SEL swizzledSelector1 = @selector(lv_viewDidDisappear:);
Method originalMethod1 = class_getInstanceMethod([self class], originalSelector1);
Method swizzledMethod1 = class_getInstanceMethod([self class], swizzledSelector1);
BOOL success1 = class_addMethod([self class], originalSelector1, method_getImplementation(swizzledMethod1), method_getTypeEncoding(swizzledMethod1));
if (success1) {
class_replaceMethod([self class], swizzledSelector1, method_getImplementation(originalMethod1), method_getTypeEncoding(originalMethod1));
} else {
method_exchangeImplementations(originalMethod1, swizzledMethod1);
}
});
}
- (void)lv_viewDidAppear:(BOOL)animated
{
[self lv_viewDidAppear:animated];
// 可以在管理類里面定義個屬性記錄當前時間
[BuryPointManager sharedInstance].time = CFAbsoluteTimeGetCurrent();
}
- (void)lv_viewDidDisappear:(BOOL)animated
{
[self lv_viewDidDisappear:animated];
// 根據(jù)頁面出現(xiàn)和消失計算瀏覽時間
NSTimeInterval time = CFAbsoluteTimeGetCurrent() - [BuryPointManager sharedInstance].time;
// 這里可以根據(jù)是哪個控制器再提交服務器, 方法里面帶上時間和需要計算時長的頁面控制器
if ([self isKindOfClass:[ViewController class]]) {
[[BuryPointManager sharedInstance] 方法];
} else if () {
} ...
// 如果每次判斷麻煩榛搔,可以在BuryPointManager里面定義一個viewControllers的數(shù)組來存需要埋點的控制器
for (NSString *name in [BuryPointManager sharedInstance].viewControllers) {
if ([name isEqualToString:NSStringFromClass([self class])]) {
[[BuryPointManager sharedInstance] 方法];
break;
}
}
}
三诺凡、點擊事件
點擊事件分為UIControl(UIButton)的點擊事件、手勢點擊事件药薯、UITableView和UICollectionView的點擊事件
- UIControl(UIButton)的點擊事件
直接hook UIControl的sendAction:to:forEvent:
方法
UIControl+BuryPoint.m
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(sendAction:to:forEvent:);
SEL swizzledSelector = @selector(lv_sendAction:to:forEvent:);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)lv_sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event {
[self lv_sendAction:action to:target forEvent:event];
// 要區(qū)分具體控件绑洛,可以用tag值來區(qū)分,最好看接口需要用什么值來標識童本,可以把這個值設(shè)為控件的tag值真屯,如果標識是字母可以先轉(zhuǎn)成16進制再轉(zhuǎn)成10進制
[[BuryPointManager sharedInstance] 方法];
}
- 手勢點擊事件
像一些控件添加手勢block就會觸發(fā),hook UITapGestureRecognizer的initWithTarget:action:
和addTarget:action:
方法
UITapGestureRecognizer+BuryPoint.m
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(initWithTarget:action:);
SEL swizzledSelector = @selector(countData_initWithTarget:action:);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
SEL originalSelector1 = @selector(addTarget:action:);
SEL swizzledSelector1 = @selector(countData_addTarget:action:);
Method originalMethod1 = class_getInstanceMethod([self class], originalSelector1);
Method swizzledMethod1 = class_getInstanceMethod([self class], swizzledSelector1);
BOOL success1 = class_addMethod([self class], originalSelector1, method_getImplementation(swizzledMethod1), method_getTypeEncoding(swizzledMethod1));
if (success1) {
class_replaceMethod([self class], swizzledSelector1, method_getImplementation(originalMethod1), method_getTypeEncoding(originalMethod1));
} else {
method_exchangeImplementations(originalMethod1, swizzledMethod1);
}
});
}
- (instancetype)countData_initWithTarget:(id)target action:(SEL)action {
[self countData_initWithTarget:target action:action];
[self addTarget:target action:action];
return self;
}
- (void)countData_addTarget:(id)target action:(SEL)action {
[self countData_addTarget:target action:action];
[self countData_addTarget:self action:@selector(countData_trackTapGestureAction:)];
}
- (void)countData_trackTapGestureAction:(UITapGestureRecognizer *)sender {
UIView *tapView = sender.view;
[[BuryPointManager sharedInstance] 方法];
}
- UITableView和UICollectionView的點擊事件
這類事件可以在UITableView和UICollectionView封裝的基類實現(xiàn)tableView:didSelectRowAtIndexPath:
collectionView:didSelectItemAtIndexPath:
方法進行埋點穷娱,也可以hook這兩個方法绑蔫。不過和上面的hook不同的是這兩個方法是代理方法,所以在替換這兩個方法前泵额,要先替換delegate的set方法setDelegate
配深,在進行tableView:didSelectRowAtIndexPath:
collectionView:didSelectItemAtIndexPath:
的方法替換。其他別人封裝的控件同理嫁盲,像SDCycleScrollView
的點擊方法篓叶,你也可以先替換它的setDelegate
,再替換點擊代理方法cycleScrollView:didSelectItemAtIndex:
,當然缸托,由于SDCycleScrollView
內(nèi)部就是一個UICollectionView左敌,你最后也可以替換collectionView:didSelectItemAtIndexPath:
。
UITableView+BuryPoint.m
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(setDelegate:);
SEL swizzledSelector = @selector(hook_setDelegate:);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
BOOL success = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)hook_setDelegate:(id<SDCycleScrollViewDelegate>)delegate {
SEL originalSelector = @selector(tableView:didSelectRowAtIndexPath:);
SEL swizzledSelector = @selector(lv_tableView:didSelectRowAtIndexPath:);
Method originalMethod_delegate = class_getInstanceMethod([delegate class], originalSelector);
Method originalMethod_self = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
//若未實現(xiàn)代理方法俐镐,則先添加代理方法
BOOL didAddOriginalMethod = class_addMethod([delegate class], originalSelector, method_getImplementation(originalMethod_self), method_getTypeEncoding(originalMethod_self));
if (!didAddOriginalMethod) {
//已經(jīng)實現(xiàn)代理方法矫限,則先將hook的方法添加到delegate class中,然后再交換Imp指針
BOOL didAddSwizzledMethod = class_addMethod([delegate class], swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddSwizzledMethod) {
Method newMethod = class_getInstanceMethod([delegate class], swizzledSelector);
method_exchangeImplementations(originalMethod_delegate, newMethod);
} else {
method_exchangeImplementations(originalMethod_delegate, swizzledMethod);
}
}
[self hook_setDelegate:delegate];
}
- (void)lv_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self lv_tableView:tableView didSelectRowAtIndexPath:indexPath];
[[BuryPointManager sharedInstance] 方法];
}