09-无侵入埋点

一、埋点方式

  • 代码埋点,手写代码进行埋点。优点是追踪精确,方便记录当前环境的变量值,易于调试。缺点是工作量大,后期难以维护。
  • 无侵入埋点,在运行时通过替换方法实现无侵入埋点。优点是能节省大量开发和维护成本。缺点是不确定性,开发成本高,不能满足所有需求。

二、无侵入埋点实现方式

利用runtime特性,在运行时通过替换方法。

2.1 如何进行方法替换

我们写一个工具类,提供方法替换的接口,方法的实现如下:

+ (void)hookForClass:(Class)targetClass fromSelector:(SEL)fromSelector toSelector:(SEL)toSelector {
    
    Method fromMethod = class_getInstanceMethod(targetClass, fromSelector);
    
    Method toMethod = class_getInstanceMethod(targetClass, toSelector);
    
    // 返回成功则表示被替换的方法没有实现,先添加实现。返回失败则表示已实现,直接进行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的分类。这里我使用的前一种方法,实现代码如下:

#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];
    // 调用原方法
    [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函数执行之前调用,会增加程序启动时间。
  • hook_viewWillAppear中我们又调用了hook_viewWillAppear,这里不会造成递归调用。原因是当我们手动调用hook_viewWillAppear时,其SEL对应的IMP已经指向了原有的方法viewWillAppear,所以实际上是执行原有viewWillAppear的IMP。

三、课后作业

实现 UITableViewCell 点击事件的无侵入埋点。

上面我们实现了对UIViewController的生命周期进行埋点,相对来说较为容易,因为方法的调用者是它本身。UITableViewCell的点击方法调用对象则是它的delegate,那么我们如何进行hook呢?

既然我们无法直接hook点击方法,那么我们就需要尝试hook点击方法之前的方法。下面就一步一步分析如何hook前一步方法。

3.1 找到点击的代理方法之前的方法

我们先写一个简单的UITableView并在其点击的代理方法中打一个断点,看看程序调用堆栈。如图:


堆栈信息

我们注意到在调用tableView:didSelectRowAtIndexPath:之前,tableView调用了一个名为_selectRowAtINdexPath:animated:scrollPosition:notifyDelegate:方法。接下来尝试hook这个方法。

3.2 解析目标方法

我们发现目标方法并没有暴露在tableView的头文件中,所以我们无法直接知道目标方法的参数类型、返回值等等信息。接下来就先进行方法解析。
分析方法的代码实现如下:

+ (void)analysisMethod:(Method)method {
    // 获取方法的参数类型
    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个参数类型为:%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));
}

调用方法如下:

 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个参数类型为:@
2019-05-06 21:52:58.836706+0800 09-无侵入埋点[19358:8173279] 第1个参数类型为::
2019-05-06 21:52:58.836856+0800 09-无侵入埋点[19358:8173279] 第2个参数类型为:@
2019-05-06 21:52:58.836966+0800 09-无侵入埋点[19358:8173279] 第3个参数类型为:B
2019-05-06 21:52:58.837076+0800 09-无侵入埋点[19358:8173279] 第4个参数类型为:q
2019-05-06 21:52:58.837175+0800 09-无侵入埋点[19358:8173279] 第5个参数类型为: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

前面两个参数我们可以不用关心,因为在方法调用时代码会被编译成类似这个样子:

((void (*)(id, SEL))objc_msgSend)((id)m, @selector(selectorName));

我们看到后面的四个参数类型分别为@、B、q、B,这些又是什么呢?
下面是官方的Type Encoding对应表
[站外图片上传中...(image-7b92da-1557152518714)]

根据Type Encoding对应表我们知道@ 表示 id对象,B表示Bool,q表示long long,以及返回值v表示void。
那么,我们的目标函数应该是这个样子:

- (void)hook_selectRowAtIndexPath:(id)indexPath animated:(BOOL)animated scrollPosition:(long long)scrollPosition notifyDelegate:(BOOL)notifyDelegate

再然后我们发现tableVIew头文件中有这个方法:

- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition

那么我们的目标函数其实可以写成这个样子:

- (void)hook_selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate

通过打印id对象也能知道其具体类型。到这里我们就已经找到并解析出需要进行hook的目标方法了。

3.3 对目标方法进行hook

同样的,创建tableView的基类或者分类来进行hook。具体代码的核心实现如下:

#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);
}

最后附上完整代码

更多详细内容,请移步至戴铭老师的专栏

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • 前言 统计埋点,作为应用功能上线前的最后一环,对于一个应用的意义是尤为重要的。如果仅仅是去完成了一个项目而不知道这...
    Leesim阅读 4,615评论 2 17
  • 前言 随着公司业务的发展,数据的重要性日益体现出来。 数据埋点的准确和全面性显得尤为重要。通过精准和详细的数据,后...
    MMR无与伦比阅读 5,918评论 2 13
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,100评论 1 32
  • 什么是埋点? 埋点是一种了解用户行为,分析用户行为,提高用户体验的一种方式。常见的解决方案有三种,代码埋点、可视化...
    我不是小白是真白阅读 1,296评论 0 2
  • 今天约了张岩老师的教练课。了解到了教练这种工具的作用,它不同于咨询,咨询告诉你方法;它不同于mentor,告诉你他...
    明哲_1826阅读 178评论 0 0