前言
在初期,沒有做好埋點(diǎn)工作母截,或者著急趕時間到忽,未能合理的做好埋點(diǎn)的工作,隨著用戶的增多微酬,就會有分析用戶的行為需求绘趋,統(tǒng)計某個頁面用戶的留存時間颤陶,雖然市面上有很多統(tǒng)計的SDK,他們大部分都是需要一個頁面一個頁面去添加陷遮,這對于程序猿來說是很不友好的滓走,工作量又大,又不好管理帽馋,突然有一天需要修改某個地方搅方,又要挨個去查找添加的埋點(diǎn)方法,去重新更改一遍绽族。怎么樣才能做好統(tǒng)一管理這些埋點(diǎn)的工作姨涡,讓他們都統(tǒng)一到一塊,又方便管理吧慢,是我們需要思考的涛漂,而且這樣也節(jié)省了大家的時間。
思考
大家都知道
objective-c
是運(yùn)行時的機(jī)制检诗,所謂運(yùn)行時就是將數(shù)據(jù)類型的確定由編譯期延遲到了運(yùn)行時匈仗,objective-c
是通過runtime
來實現(xiàn)的,它是一個非常強(qiáng)大的C語言
庫 逢慌,這個代碼很早以前就開源了悠轩,想要了解objective-c
,可以看看Apple的Github和
Apple opensource開源代碼攻泼。我們平時所編寫的objective-c
代碼火架,會在運(yùn)行時轉(zhuǎn)換成runtime
的c
語言代碼,objective-c
通過runtime
創(chuàng)建類跟對象忙菠,并進(jìn)行消息的發(fā)送與轉(zhuǎn)發(fā)何鸡。
在做無侵入埋點(diǎn)的同時,我們需要了解下我們做埋點(diǎn)統(tǒng)計時需要在什么地方進(jìn)行埋點(diǎn)統(tǒng)計只搁。
以下是我的埋點(diǎn)思路
實踐
我們確定了需要在什么地方進(jìn)行埋點(diǎn)音比,接下來就開始實踐,
Show me your code
首先我們寫個工具類用來統(tǒng)計頁面
///后期用到交換方法比較多氢惋,統(tǒng)一一個函數(shù)進(jìn)行方法交換
- (void)ljl_exchangeMethodWithClass:(Class)cls
originalSEL:(SEL)originalSEL
changeSEL:(SEL)changeSEL{
Method originalMethod = class_getInstanceMethod(cls, originalSEL);
Method changeMethod = class_getInstanceMethod(cls, changeSEL);
method_exchangeImplementations(originalMethod, changeMethod);
}
記錄打印日志統(tǒng)一管理
- (void)recordHookClass:(Class)cls identifier:(NSString *)identifier{
NSLog(@"當(dāng)前類名:%@",NSStringFromClass(cls));
NSLog(@"標(biāo)識符:%@",identifier);
}
UIViewController統(tǒng)計
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
///獲取
SEL willAppear = @selector(viewWillAppear:);
SEL hook_willAppear = @selector(hook_viewWillAppear:);
[[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:willAppear changeSEL:hook_willAppear];
SEL disappear = @selector(viewDidDisappear:);
SEL hook_disappear = @selector(hook_viewDidDisappear:);
[[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:disappear changeSEL:hook_disappear];
});
}
方法實現(xiàn)
- (void)hook_viewWillAppear:(BOOL)animated{
[[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:@"進(jìn)入"];
[self hook_viewWillAppear:animated];
}
- (void)hook_viewDidDisappear:(BOOL)animated{
[[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:@"離開"];
[self hook_viewDidDisappear:animated];
}
此方案只是針對用戶的停留時間及用戶的進(jìn)入次數(shù),日志打印可按需求來統(tǒng)計稽犁,不同的需求進(jìn)行不同的方式焰望。
UITableView
UITableView
與UICollectionView
統(tǒng)計用戶點(diǎn)擊cell的方法都是在代理中,我們需要進(jìn)行替換設(shè)置delegate
的方法已亥,在熊赖、setDelegate:
方法中插入統(tǒng)計的代碼,這里有個小坑虑椎,有的頁面是沒有實現(xiàn)didSelectRowAtIndexPath
震鹉,為了使得方法不交換可以判斷下是否實現(xiàn)了didSelectRowAtIndexPath
再進(jìn)行統(tǒng)計操作俱笛。
Code
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSEL = @selector(setDelegate:);
SEL changeSEL = @selector(hook_setDelegate:);
[[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
});
}
函數(shù)實現(xiàn)
- (void)hook_setDelegate:(id<UITableViewDelegate>)delegate{
[self hook_setDelegate:delegate];
Method didSelectmethod = class_getInstanceMethod(delegate.class, @selector(tableView:didSelectRowAtIndexPath:));
IMP hookIMP = class_getMethodImplementation(self.class, @selector(hook_tableView:didSelectRowAtIndexPath:));
char const* type = method_getTypeEncoding(didSelectmethod);
class_addMethod(delegate.class, @selector(hook_tableView:didSelectRowAtIndexPath:), hookIMP, type);
Method hookMethod = class_getInstanceMethod(delegate.class, @selector(hook_tableView:didSelectRowAtIndexPath:));
method_exchangeImplementations(didSelectmethod, hookMethod);
}
- (void)hook_tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[[LJL_HookObjcLog logManage] recordHookClass:self.class identifier:[NSString stringWithFormat:@"%ld,%ld",indexPath.row,indexPath.section]];
[self hook_tableView:tableView didSelectRowAtIndexPath:indexPath];
}
UIButton的點(diǎn)擊事件
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
///獲取
SEL originalSEL = @selector(sendAction:to:forEvent:);
SEL changeSEL = @selector(hook_sendAction:to:forEvent:);
[[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
});
}
///MAKR:
- (void)hook_sendAction:(SEL)action
to:(nullable id)target
forEvent:(nullable UIEvent *)event{
[self hook_sendAction:action to:target forEvent:event];
///點(diǎn)擊事件結(jié)束記錄
if ([[event.allTouches anyObject]phase] == UITouchPhaseEnded) {
[[LJL_HookObjcLog logManage] recordLogActionHookClass:[target class] action:action identifier:@"UIButton"];
}
}
UIGestureRecognizer手勢的Hook方法
@implementation UIGestureRecognizer (Log_Category)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
///獲取
SEL originalSEL = @selector(initWithTarget:action:);
SEL changeSEL = @selector(hook_initWithTarget:action:);
[[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:self originalSEL:originalSEL changeSEL:changeSEL];
});
}
- (instancetype)hook_initWithTarget:(nullable id)target action:(nullable SEL)action{
UIGestureRecognizer *gestureRecognizer = [self hook_initWithTarget:target action:action];
SEL changeSEL = @selector(hook_gestureAction:);
IMP hookIMP = class_getMethodImplementation(self.class, changeSEL);
const char *type = method_getTypeEncoding(class_getInstanceMethod([target class], action));
class_addMethod([target class], changeSEL, hookIMP, type);
[[LJL_HookObjcLog logManage]ljl_exchangeMethodWithClass:[target class] originalSEL:action changeSEL:changeSEL];
return gestureRecognizer;
}
- (void)hook_gestureAction:(id)sender{
[self hook_gestureAction:sender];
[[LJL_HookObjcLog logManage] recordLogActionHookClass:[sender class] action:@selector(action) identifier:@"手勢"];
}
@end
思考總結(jié)
本文簡單講述無侵入埋點(diǎn)的統(tǒng)計方案,思路大致上是通過
Runtime
的運(yùn)行機(jī)制传趾,在運(yùn)行期可以向類中新增或替換選擇子所對應(yīng)的方法實現(xiàn)迎膜。使用另外一份實現(xiàn)原有的方法實現(xiàn)。
在無侵入的基礎(chǔ)上浆兰,即降低了代碼的耦合磕仅,又方便了后期維護(hù)管理,相對于可視化埋點(diǎn)簸呈,方便簡單榕订,所有方式都會有優(yōu)點(diǎn)與缺點(diǎn)。
本文描述的優(yōu)點(diǎn)就是無侵入蜕便,低耦合劫恒,好管理維護(hù)。
缺點(diǎn):有些頁面是復(fù)用機(jī)制轿腺,比如cell的復(fù)用两嘴,一個控制器可能多次進(jìn)入,需要我們做好統(tǒng)一管理的標(biāo)識符吃溅,一個button的點(diǎn)擊需要遞歸獲取當(dāng)前的控制器等操作溶诞。有些模塊可能會出現(xiàn)統(tǒng)計不準(zhǔn)確等因素,還有可能團(tuán)隊人員多了决侈,定義的方法有時候都是一致的螺垢,這樣對于這種無侵入的方式最終的效果是不太準(zhǔn)確的。相比較可視化埋點(diǎn)赖歌,數(shù)據(jù)統(tǒng)計的更加合理枉圃,準(zhǔn)確,維護(hù)成本略高
每個項目所要統(tǒng)計的內(nèi)容不一致庐冯,精確的程度也不一樣孽亲,都是各自的觀點(diǎn),本文只是自己的理解與記憶展父,如果你又什么更好的方案可以留言分享返劲,謝謝。
可參考鏈接
有貨 iOS 數(shù)據(jù)非侵入式自動采集探索實踐
網(wǎng)易HubbleData無埋點(diǎn)SDK在iOS端的設(shè)計與實現(xiàn)