一龙致、埋點方式
- 代碼埋點,手寫代碼進行埋點顷链。優(yōu)點是追蹤精確目代,方便記錄當前環(huán)境的變量值,易于調(diào)試嗤练。缺點是工作量大榛了,后期難以維護。
- 無侵入埋點煞抬,在運行時通過替換方法實現(xiàn)無侵入埋點忽冻。優(yōu)點是能節(jié)省大量開發(fā)和維護成本。缺點是不確定性此疹,開發(fā)成本高僧诚,不能滿足所有需求遮婶。
二、無侵入埋點實現(xiàn)方式
利用runtime特性湖笨,在運行時通過替換方法旗扑。
2.1 如何進行方法替換
我們寫一個工具類,提供方法替換的接口慈省,方法的實現(xiàn)如下:
+ (void)hookForClass:(Class)targetClass fromSelector:(SEL)fromSelector toSelector:(SEL)toSelector {
Method fromMethod = class_getInstanceMethod(targetClass, fromSelector);
Method toMethod = class_getInstanceMethod(targetClass, toSelector);
// 返回成功則表示被替換的方法沒有實現(xiàn)臀防,先添加實現(xiàn)。返回失敗則表示已實現(xiàn)边败,直接進行IMP指針交換
if (class_addMethod(targetClass, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
// 進行方法替換
class_replaceMethod(targetClass, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
}else {
// 交換IMP指針
method_exchangeImplementations(fromMethod, toMethod);
}
}
2.2 如何進行hook
以UIViewController的viewWillAppear和viewWillDisappear方法為例袱衷,建一個UIViewController的基類,讓項目中用到的controller都繼承自它笑窜,或者使用UIViewController的分類致燥。這里我使用的前一種方法,實現(xiàn)代碼如下:
#import "JCBaseViewController.h"
#import "JCHook.h"
@implementation JCBaseViewController
#pragma mark - initialize
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[JCHook hookForClass:self fromSelector:@selector(viewWillAppear:) toSelector:@selector(hook_viewWillAppear:)];
[JCHook hookForClass:self fromSelector:@selector(viewWillDisappear:) toSelector:@selector(hook_viewWillDisappear:)];
});
}
#pragma mark - hook method
- (void)hook_viewWillAppear:(BOOL)animated {
// 埋點代碼
[self insertViewWillAppear];
// 調(diào)用原方法
[self hook_viewWillAppear:animated];
}
- (void)hook_viewWillDisappear:(BOOL)animated {
[self insertViewWillDisappear];
[self hook_viewWillDisappear:animated];
}
#pragma mark - private Methods
- (void)insertViewWillAppear {
NSLog(@"%@ && %s",NSStringFromClass([self class]),__func__);
}
- (void)insertViewWillDisappear {
NSLog(@"%@ && %s",NSStringFromClass([self class]),__func__);
}
@end
這里有兩個地方需要注意一下排截。
- 其實在initialize和load里都可以進行hook嫌蚤,之所以選用initialize是因為load方法是在main函數(shù)執(zhí)行之前調(diào)用,會增加程序啟動時間断傲。
- hook_viewWillAppear中我們又調(diào)用了hook_viewWillAppear脱吱,這里不會造成遞歸調(diào)用。原因是當我們手動調(diào)用hook_viewWillAppear時认罩,其SEL對應(yīng)的IMP已經(jīng)指向了原有的方法viewWillAppear箱蝠,所以實際上是執(zhí)行原有viewWillAppear的IMP。
三垦垂、課后作業(yè)
實現(xiàn) UITableViewCell 點擊事件的無侵入埋點抡锈。
上面我們實現(xiàn)了對UIViewController的生命周期進行埋點,相對來說較為容易乔外,因為方法的調(diào)用者是它本身床三。UITableViewCell的點擊方法調(diào)用對象則是它的delegate,那么我們?nèi)绾芜M行hook呢杨幼?
既然我們無法直接hook點擊方法撇簿,那么我們就需要嘗試hook點擊方法之前的方法。下面就一步一步分析如何hook前一步方法差购。
3.1 找到點擊的代理方法之前的方法
我們先寫一個簡單的UITableView并在其點擊的代理方法中打一個斷點四瘫,看看程序調(diào)用堆棧。如圖:
我們注意到在調(diào)用tableView:didSelectRowAtIndexPath:之前欲逃,tableView調(diào)用了一個名為_selectRowAtINdexPath:animated:scrollPosition:notifyDelegate:方法找蜜。接下來嘗試hook這個方法。
3.2 解析目標方法
我們發(fā)現(xiàn)目標方法并沒有暴露在tableView的頭文件中稳析,所以我們無法直接知道目標方法的參數(shù)類型洗做、返回值等等信息弓叛。接下來就先進行方法解析。
分析方法的代碼實現(xiàn)如下:
+ (void)analysisMethod:(Method)method {
// 獲取方法的參數(shù)類型
unsigned int argumentsCount = method_getNumberOfArguments(method);
char argName[512] = {};
for (unsigned int j = 0; j < argumentsCount; ++j) {
method_getArgumentType(method, j, argName, 512);
NSLog(@"第%u個參數(shù)類型為:%s", j, argName);
memset(argName, '\0', strlen(argName));
}
char returnType[512] = {};
method_getReturnType(method, returnType, 512);
NSLog(@"返回值類型:%s", returnType);
// type encoding
NSLog(@"TypeEncoding: %s", method_getTypeEncoding(method));
}
調(diào)用方法如下:
Method method = class_getInstanceMethod(self, @selector(_selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:));
[self analysisMethod:method];
控制臺輸出如下:
2019-05-06 21:52:58.836494+0800 09-無侵入埋點[19358:8173279] 第0個參數(shù)類型為:@
2019-05-06 21:52:58.836706+0800 09-無侵入埋點[19358:8173279] 第1個參數(shù)類型為::
2019-05-06 21:52:58.836856+0800 09-無侵入埋點[19358:8173279] 第2個參數(shù)類型為:@
2019-05-06 21:52:58.836966+0800 09-無侵入埋點[19358:8173279] 第3個參數(shù)類型為:B
2019-05-06 21:52:58.837076+0800 09-無侵入埋點[19358:8173279] 第4個參數(shù)類型為:q
2019-05-06 21:52:58.837175+0800 09-無侵入埋點[19358:8173279] 第5個參數(shù)類型為:B
2019-05-06 21:52:58.837277+0800 09-無侵入埋點[19358:8173279] 返回值類型:v
2019-05-06 21:52:58.837377+0800 09-無侵入埋點[19358:8173279] TypeEncoding: v40@0:8@16B24q28B36
前面兩個參數(shù)我們可以不用關(guān)心诚纸,因為在方法調(diào)用時代碼會被編譯成類似這個樣子:
((void (*)(id, SEL))objc_msgSend)((id)m, @selector(selectorName));
我們看到后面的四個參數(shù)類型分別為@撰筷、B、q畦徘、B毕籽,這些又是什么呢?
下面是官方的Type Encoding對應(yīng)表
[站外圖片上傳中...(image-7b92da-1557152518714)]
根據(jù)Type Encoding對應(yīng)表我們知道@ 表示 id對象井辆,B表示Bool关筒,q表示long long,以及返回值v表示void杯缺。
那么蒸播,我們的目標函數(shù)應(yīng)該是這個樣子:
- (void)hook_selectRowAtIndexPath:(id)indexPath animated:(BOOL)animated scrollPosition:(long long)scrollPosition notifyDelegate:(BOOL)notifyDelegate
再然后我們發(fā)現(xiàn)tableVIew頭文件中有這個方法:
- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
那么我們的目標函數(shù)其實可以寫成這個樣子:
- (void)hook_selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate
通過打印id對象也能知道其具體類型。到這里我們就已經(jīng)找到并解析出需要進行hook的目標方法了夺谁。
3.3 對目標方法進行hook
同樣的廉赔,創(chuàng)建tableView的基類或者分類來進行hook肉微。具體代碼的核心實現(xiàn)如下:
#pragma mark - initialize
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method method = class_getInstanceMethod(self, @selector(_selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:));
[self analysisMethod:method];
[JCHook hookForClass:self fromSelector:@selector(_selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:) toSelector:@selector(hook_selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:)];
});
}
#pragma mark - hook method
- (void)hook_selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
[self insertTableViewDidSelectIndexPath:indexPath];
[self hook_selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition notifyDelegate:notifyDelegate];
}
#pragma mark - private Methods
- (void)insertTableViewDidSelectIndexPath:(NSIndexPath *)indexPath {
NSLog(@"%@",indexPath);
}
最后附上完整代碼
更多詳細內(nèi)容匾鸥,請移步至戴銘老師的專欄