iOS 应用性能数据采集原理和优化实践 | 详细版
云智慧集团成立于 2009 年,是全栈智能业务运维解决方案服务商。经过多年自主研发,公司形成了从 IT 运维、电力运维到 IoT 运维的产业布局,覆盖 ITOM、ITOA、ITSM、DevOps 以及 IoT 几大领域,为金融、政府、运营商、能源、交通、制造等上百家行业的客户,提供了数字化运维体系建设及全生命周期运维管理解决方案。云智慧秉承 Make Digital Online 的使命,致力于通过先进的产品技术,为企业数字化转型和提升 IT 运营效率持续赋能。
作者简介
刘徐兵(Alvin Liu),云智慧/开发经理。曾在高德、当当有多年大型 App 开发经验,在云智慧从事 APM SDK 研发工作 5+年。对 App 开发和性能优化有深入的研究和实践。
iOS 应用数据采集的基础 Objective-C Runtime
1、消息转发
Objective-C 语言扩展了 C 语言,扩展的核心在于引入了 Runtime 库,使 Objective-C 语言拥有了面向对象和动态运行时的特性。而动态运行时机制的核心和表现是消息转发机制。
Objective-C 语言拥有动态运行时的机制,方法的执行是在运行阶段决定的而不是在编译阶段决定的。而方法执行的实质是向对象发送了一个消息,官方 API 为
objc_msgSend 有 2 个常用参数:id self 和 SEL op,用于标识对象和方法,即向某个对象发送了某个消息。因此 Objective-C 的[instance method]调用会被编译器转换成 C 语言 API objc_msgSend 的调用。
以下消息转发机制原理图从官方文档翻译而来:
消息转发机制示例图
如上图所示,Objective-C 的消息转发流程中,在当前对象方法列表中找不到方法的实现时,运行时环境会依次进行三个阶段的查找
第一阶段
对象在收到无法处理的消息时,首先调用所属类的下列类方法。
如果在其中找到了方法的实现,则进行消息处理;否则就进入第二阶段。
第二阶段
在当前类里找不到该方法的实现时,运行时系统尝试更换调用的对象,会调用如下方法
如果在该方法中还找不到方法的实现,就进入第三阶段。
第三阶段
到这里是最后一个阶段,通过创建 NSInvocation 实例,将与未处理的消息有关的细节封装起来,运行时系统调用的接口为
该方法会沿着类的继承链一直往上调用,直至在 NSObject 的该方法中抛出 doesNotRecognizeSelector:异常。
2、函数指针
SEL 是 Objective-C 语言的方法选择器,也就是 selector 的指针。再往底层去,每个方法 SEL 还对应着一个 IMP 函数指针,指向方法实现的首地址。官方 API 为:
有了它就可以直接执行 IMP 指向的函数(方法)了。
以上介绍了理论基础,下面介绍下基于 Objective-C 语言动态运行时的方法拦截(Hook)操作。
Hook 原理
Hook 步骤
使用 Category 特性往类中添加用于拦截的方法 SEL
使用系统 runtime 的接口(Swizzle)交换方法实现体(IMP)
Hook 使用示例图
如图所示,开发者调用原有方法时,会转发到拦截的方法里,因交换了原方法与拦截方法的入口地址(IMP),在拦截方法执行结束时能调回原方法,对原有业务没有影响。
Hook 回调函数
难点:不是类的实例方式,不能用 Category 特性。
优化前:全工程扫描,再拦截。
缺点:只能在主线程中操作,扫描文件数和消耗的时间与 App 的规模大小成正比。
回调函数拦截优化
步骤:
针对要使用 protocol 的系统类使用 Category 特性添加拦截方法
拦截设置 delegate 的 setter 方法,获取到 delegate 的实例
针对获取到的 delegate 实例再进行回调方法拦截
优化后
1、延迟拦截:在方法被调用时才被拦截而且只拦截一次
2、SDK 的启动操作跟 App 的业务和规模无关,对 App 的影响降到最低
崩溃解码实践
目的:了解 iOS 崩溃解码原理
崩溃解码步骤
虚拟内存偏移量:145624->0x238d8->0x00000001000238d8
使用 dwarfdump 命令,将 dSYM 文件解析成可读文件
dwarfdum-e--debug-info 包含全部类和方法的代码和地址映射信息->信息文件
dwarfdum-e--debug-line 包含全部类的代码行号和地址映射信息->行号文件
在信息文件中查找偏移量所在的类和方法,得到崩溃的类和对应的方法
在行号文件中查找偏移量所在的行号,得到具体的崩溃行号
虚拟内存偏移量:
0x00000001000238d8
dSYM 文件解析命令
symbolicatecrash->全文本解析
atos(mac)/atosl(linux)->单行解析
H5 页面监控原理
以上介绍了系统原生接口的数据采集原理,下面介绍下 H5 页面数据采集实现原理。
H5 页面数据采集通过自动往 H5 页面注入 JS 代码实现。流程如下图所示
H5 页面注入 JS 代码示意图
如上图所示,在 UIWebView 时代,通过 NSURLProtocol 协议簇的接口,拦截到加载页面数据的接口,获取到 H5 数据块,检测数据块中的<head>标签,将 JS 代码注入到<head>中。这样 JS 代码就和 H5 页面代码一起加载、工作,能够采集到 H5 页面的相关信息。JS 代码采集到信息后,通过 iframe 的方式触发 UIWebView 的回调将信息发送给原生 SDK 端存储、上报。
在 WKWebView 时代,由于 WKWebView 是单独的进程,在 App 里从系统的网络协议簇无法获取到 WKWebView 的数据,可通过拦截 WKWebView 的回调函数去执行 JS 代码,能起到与 UIWebView 同样的数据采集效果。这里不再赘述。
PS:JS 代码的工作原理是另一个技术领域的范畴,不在这里赘述。
写在最后
近年来,在 AIOps 领域快速发展的背景下,IT 工具、平台能力、解决方案、AI 场景及可用数据集的迫切需求在各行业迸发。基于此,云智慧在 2021 年 8 月发布了 AIOps 社区,旨在树起一面开源旗帜,为各行业客户、用户、研究者和开发者们构建活跃的用户及开发者社区,共同贡献及解决行业难题、促进该领域技术发展。
社区先后开源了数据可视化编排平台-FlyFish、运维管理平台 OMP、云服务管理平台-摩尔平台、Hours 算法等产品。
项目介绍:https://www.cloudwise.ai/flyFish.html
Github 地址: https://github.com/CloudWise-OpenSource/FlyFish
Gitee 地址: https://gitee.com/CloudWise/fly-fish
请您通过下方链接了解我们,添加小助手微信(xiaoyuerwie),备注:飞鱼。申请加入开发者交流群,可与业内大咖进行 1V1 交流!
也可通过下方微信获取 AIOps 资讯,了解云智慧开源社区其他项目开源情况!
评论