iOS用户行为追踪——无侵入埋点

article/2025/9/22 6:17:49

  本文章系作者原创文章,如需转载学习,请注明该文章的原始出处和网址链接。
  在阅读的过程中,如若对该文章有不懂或值得优化的建议,欢迎大家加QQ:690091622 进行技术交流和探讨。


前言:
  前几日做项目,需要做这样的一个功能:
    记录应用Crash之前用户操作的最后20步
  看到这样的需求,第一感觉就是有些懵,excuse me? 用户咋操作的我咋知道???应用啥时候Crash我咋知道???

  最后,经过各方查找资料,终于搞定了。
  先不多说,放一张控制台输出的运行结果的截图。


User_Trace_Sequence.jpg

1. 技术原理

 1.1 Method-Swizzling

  在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。
  利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法hook的目的。
  每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。


IMP.jpg
  1. 用 method_exchangeImplementations 方法来交换2个方法中的IMP,
  2. 用 class_replaceMethod 方法来修改类,
  3. 用 method_setImplementation 方法来直接设置某个方法的IMP,

  其实,就是在程序运行中偷换了selector的IMP,如下图所示:


IMP_exchange.jpg

 1.2 Target-Action

  对于一个给定的事件,UIControl会调用sendAction:to:forEvent:来将行为消息转发到UIApplication对象,再由UIApplication对象调用其sendAction:to:fromSender:forEvent:方法来将消息分发到指定的target上,而如果我们没有指定target,则会将事件分发到响应链上第一个想处理消息的对象上。
  而如果子类想监控或修改这种行为的话,则可以重写这个方法。

2.实现分析

  用户的操作行为轨迹在应用上的体现无非就是以下这几种情况:

  • 点击了哪个按钮
  • 哪个页面跳转到哪个页面
  • 当前停留在是哪个界面 

  1. 对于我们需要实现的功能中关于记录用户交互的操作,我们使用runtime中的方法hook下sendAction:to:forEvent:便可以知道用户进行了什么样的交互操作。
这个方法对UIControl及继承于UIControl而实现的子类对象是有效的,比如UIButton、UISlider、UIDatePicker、UISegmentControl等。
  2. iOS中页面切换有两种方式:UIViewController中的presentViewController:animated:dismissViewController:completion:;UINavigationController中的pushViewController:animated:popViewControllerAnimated:
  但是,对于UIViewController来说,我们不对这两个方法hook,因为页面跳来跳去,记录下来的各种数据会很多很乱,不利于后续查看。所以hook下ViewDidAppear:这个方法知道哪个页面显示了就足够了,而所有显示的页面按时间顺序连成序列,便是用户操作后应用中的页面跳转的轨迹。

  这个解决方案看起来很不错,这样既没有在项目中到处插入埋点函数,也没有给项目增加多少代码量,是一个两全其美的办法。

3. 代码实现

  以下是对三个类进行hook的主要实现代码。

 3.1. UIApplication

@interface UIApplication (HLCHook)
+ (void)hookUIApplication;
@end


@implementation UIApplication (HLCHook)
+ (void)hookUIApplication
{
Method controlMethod = class_getInstanceMethod([UIApplication class], @selector(sendAction:to:from:forEvent:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_sendAction:to:from:forEvent:));
method_exchangeImplementations(controlMethod, hookMethod);
}


- (BOOL)hook_sendAction:(SEL)action to:(nullable id)target from:(nullable id)sender forEvent:(nullable UIEvent *)event;
{
NSString *actionDetailInfo = [NSString stringWithFormat:@" %@ - %@ - %@", NSStringFromClass([target class]), NSStringFromClass([sender class]), NSStringFromSelector(action)];
NSLog(@"%@", actionDetailInfo);
return [self hook_sendAction:action to:target from:sender forEvent:event];
}
@end

 3.2. UIViewController

@interface UIViewController (HLCHook)
+ (void)hookUIViewController;
@end


@implementation UIViewController (HLCHook)
+ (void)hookUIViewController
{
Method appearMethod = class_getInstanceMethod([self class], @selector(viewDidAppear:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_ViewDidAppear:));
method_exchangeImplementations(appearMethod, hookMethod);
}


- (void)hook_ViewDidAppear:(BOOL)animated
{
NSString *appearDetailInfo = [NSString stringWithFormat:@" %@ - %@", NSStringFromClass([self class]), @"didAppear"];
NSLog(@"%@", appearDetailInfo);
[self hook_ViewDidAppear:animated];
}
@end

 3.3. UINavigatinoController

@interface UINavigationController (HLCHook)
+ (void)hookUINavigationController_push;
+ (void)hookUINavigationController_pop;
@end


@implementation UINavigationController (HLCHook)
+ (void)hookUINavigationController_push
{
Method pushMethod = class_getInstanceMethod([self class], @selector(pushViewController:animated:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_pushViewController:animated:));
method_exchangeImplementations(pushMethod, hookMethod);
}


- (void)hook_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
NSString *popDetailInfo = [NSString stringWithFormat: @"%@ - %@ - %@", NSStringFromClass([self class]), @"push", NSStringFromClass([viewController class])];
NSLog(@"%@", popDetailInfo);
[self hook_pushViewController:viewController animated:animated];
}


+ (void)hookUINavigationController_pop
{
Method popMethod = class_getInstanceMethod([self class], @selector(popViewControllerAnimated:));
Method hookMethod = class_getInstanceMethod([self class], @selector(hook_popViewControllerAnimated:));
method_exchangeImplementations(popMethod, hookMethod);
}


- (void)hook_popViewControllerAnimated:(BOOL)animated
{
NSString *popDetailInfo = [NSString stringWithFormat:@"%@ - %@", NSStringFromClass([self class]), @"pop"];
NSLog(@"%@", popDetailInfo);
[self hook_popViewControllerAnimated:animated];
}
@end


  至此,核心代码已经完成了。
  那么如何使用该功能来记录用户操作轨迹呢?
  在appDelegate.m文件中的 application:didFinishLaunchingWithOptions: 添加如下四行代码: 
    [UIApplication hookUIApplication];
[UIViewController hookUIViewController];
[UINavigationController hookUINavigationController_push];
[UINavigationController hookUINavigationController_pop];

  启动程序,并观察控制台输出,神奇的事情将会发生,用户的每一次操作和页面跳转都会被记录下来。

提醒

1.UITabBarItem
  当用户点击了UITabBarItem时,会同时记录三次事件,分别是:

  • _buttonDown:
  • _buttonUp:
  • _tabBarItemClicked:

  所以,对于这三个事件,我们可以只需保留一个,将其他两个在记录的时候过滤掉。若记录空间有限,过滤掉冗余的信息,这样可以在有限的记录空间上记录更多的用户操作数据。

总结

  1.hook方式非常强大,几乎可以截取任何用户想截取的消息事件,但是,每次触发hook,必然存在置换IMP整个过程,频繁的置换IMP必然会影响到应用及手机资源的消耗,不到非不得已,建议少用。
  2.什么时候用hook的方式来埋点呢?例如,当应用有10个页面,而我们只需在其中两个页面上埋点,那么就没必要用这种方式了。具体什么时候用,由开发者根据项目实际需求来权衡,我们的原则就是要力图资源消耗最少。
  3.对于View上的手势触摸事件touchBegan:withEvent:等,这种方式截取不到消息。之所以暂时不做,也是因为消耗的问题,因为苹果手机都是触摸屏的,每进行一次触摸屏幕,不管会不会产生交互事件都会触发该事件的。有兴趣的小伙伴可以根据以上提供的思路来自己尝试实现下,测试下系统消耗,看适不适合来做。


    http://chatgpt.dhexx.cn/article/9pEJK28w.shtml

    相关文章

    猫猫学习ios 之第三方登录友盟实现

    一:集成友盟分享 做第三方登录现在大多数用友盟,友盟之中做第三方登录的时候首先下载sdk,然后自己看文档,其实友盟的官方文档写的已经十分清楚了,这里自己写写,做一下笔记 二:详细 友盟&#…

    【flutter】使用permission_handler配置android和 iOS的权限

    文章目录 前言准备工作一、使用步骤1.使用的插件2.配置权限 二、代码示例三、结果截图 前言 flutter在pub.flutter-io.cn插件库中有很多的关于权限配置的插件,但是就我个人而言,比较推荐使用permission_handler这个插件。当我们打开permission_handler时…

    Flutter 混合开发 - 03 百度地图定位功能 ios 篇

    本节目标 创建 ios flutter 插件流程集成百度定位功能 视频 https://www.bilibili.com/video/BV1HT4y1L73i/ 代码 https://github.com/ducafecat/flutter_baidu_plugin_ducafecat/releases/tag/v1.0.3 百度平台部分 设置 AK https://lbsyun.baidu.com/apiconsole/key#/h…

    iOS-Charts图表绘制一块平行X轴线性指标

    养小猫咪的伙伴来我的店铺逛逛吧!抖音商城搜索#早睡早起的猫咪小铺子 最近做项目需要画柱状图和折线图,引入了第三方的图标库Charts。 这个图表库基本上能够满足大家对于图表绘制的需要,但是api接口的解释并不是很详细,该库有强大的功能&…

    ios模拟器 - Simulator录制视频

    养小猫咪的伙伴来我的店铺逛逛吧!抖音商城搜索#早睡早起的猫咪小铺子 1.进入终端,cd到要放置录屏文件的位置 2.输入命令 ,输入你的命名 xcrun simctl io booted recordVideo xxx.mov 提示:停止录屏 control c 最后进入到对应文件夹就可以找到录制好的…

    猫猫学iOS之安装cocoapods

    啥事cocoa pods 不解释,自己看这里只有一次安装流程,猫猫的安装流程。 打开命令行,我用的是ruby安装,mac自带ruby,啥是ruby,不解释,因为开始我也不懂,就当他是命令行。 1&#xff…

    基于Java的在线聊天APP系统分析及设计

    基于Java的在线聊天APP系统分析及设计 目录 基于Java的在线聊天APP系统分析及设计 1 一、 需求分析 3 核心用户分析 3系统的主要功能的概述 3项目操作流程图 4功能详解 4 登录 4注册 4消息盒子 4好友盒子 4好友列表 4朋友验证 4我的账号 4新的朋友 5验证消息 5好友资料卡 5删…

    Taro+react仿微信app聊天室|taro仿微信界面|taro聊天/朋友圈

    基于TaroreactreduxRNtaroPop等技术开发的跨端聊天App实例,支持编译到多端H5小程序RN端,界面仿制微信聊天界面,实现了消息发送、表情、图片预览、长按菜单、红包、朋友圈等功能。 Taro三端统一聊天应用:taro-chatroom (仿微信界面…

    android机器人聊天软件,虚拟男友聊天机器人

    虚拟男友聊天机器人是一款能为大家提供专业的虚拟聊天软件,在这里大家可以设定一个符合自己心意的男友,让你们之间的对话是充满了甜蜜,并且还可以自己设定回复方式,对话也是十分的轻松愉悦,快来下载虚拟男友聊天机器人…

    uniapp开发即时通讯聊天app,纯nvue仿微信,前后端开源

    github地址:GitHub - guipie/GpChat: uniapp开发的纯nvue的即时聊天通讯App。 gitee:https://gitee.com/chenwei_zq/GpChat uniapp开发的纯nvue的即时聊天通讯App。 后台采用.net6,一套解决方案,分布式部署。 App采用uniapp的纯nvue&#x…

    使用dialogflow和firebase构建whatsapp聊天机器人的指南

    ChatBots are conversational agents, programs capable of conducting a conversation with an Internet user. In this tutorial I’ll walk you through an implementation of WhatsApp chatbot using Twilio platform. ChatBots是对话代理,是能够与Internet用户…

    【uni-app】uni-app实现聊天页面功能(小程序)——布局篇

    文章目录 前言划分区域问题内容溢出关于调试聊天框 代码实现 前言 在工作中使用uni-app参与开发一个小程序,其中要写一个简单的聊天页面,虽然功能不多(只有一个发送文字的功能),但是其中的细节比较多,也踩…

    如何搜索WhatsApp聊天消息

    Trying to find a specific message in your huge WhatsApp chat log? There are two ways to search, so you can find what you’re looking for quickly. 试图在庞大的WhatsApp聊天日志中查找特定消息? 有两种搜索方式,因此您可以快速找到要查找的内…

    【uni-app】uni-app实现聊天页面功能——功能篇(下)

    目录 前言一、聊天框随键盘抬起思路代码实现 二、聊天消息列表随着聊天框的增高而滚动到最底部思路 三、问题完整代码实现总结 前言 前面我有写关于如何进行聊天页面布局和实现聊天消息滚动到最底部的文章。 【uni-app】uni-app实现聊天页面功能——功能篇(上&…

    如何将 WhatsApp 聊天添加到您的网站

    WhatsApp是全球最受欢迎的消息传递应用程序。平台上有超过 2 亿活跃用户与朋友、家人和企业进行交流。对于企业而言,WhatsApp 是与客户进行个人、可访问和非正式对话的理想渠道。 要将 WhatsApp 作为渠道引入您的客户旅程,第一步是将 WhatsApp 聊天按钮…

    uni-app 即时聊天

    项目介绍 前段时间在B站看到了有一个UP主在讲uni-app即时聊天的项目(逸刻时光),在看了这个视频之后,感觉还是挺有兴趣的,所以在看他的讲解视频之后,就自己动手写了这个即时聊天项目,在样式方面…

    uniapp+nvue实现仿微信App聊天应用 —— 成功实现好友聊天+语音视频通话功能

    基于uniapp nvue实现的uniapp仿微信App聊天应用 txim 实例项目,实现了以下功能。 1: 聊天会话管理 2: 好友列表 3: 文字、语音、视频、表情、位置等聊天消息收发 4: 一对一语音视频在线通话 技术实现 开发环境:HbuilderX nodejs技术框架&#xff…

    WhatsApp聊天记录迁移新手机,备份如何找回和删除?

    当外贸人更换手机时,面临的第一大问题就是WhatsApp数据迁移的问题。 因为WhatsApp的聊天记录、联系人、图片、视频等,都是与客户息息相关的数据,对外贸人来讲都非常重要,所以一定要确保在换手机过程中不丢失WhatsApp的任何资料。…

    [uni-app]聊天App实例

    项目简介 基于uni-appvuevuexuniPopswiper等技术开发的仿微信聊天室uniapp-chatroom项目,类似vue及小程序api语法使开发更加方便,实现了发送图文消息、表情(gif动图),图片预览、地图位置、红包、仿微信朋友圈等功能 效果图 在H5 / 小程序 …

    开发社交聊天APP需要注意什么?如何快速开发聊天功能

    随着互联网的发展,人们的沟通方式也在悄悄发生变化,由原来的面对面沟通,发展为网上沟通。让大家日常生活的通讯越来越方便了,各种APP层出不穷。那么,想开发一款社交聊天并进行运营,需要注意哪些方面&#x…