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(void /* id self, SEL op, ... */ )
objc_msgSend有2个常用参数:id self 和 SEL op,用于标识对象和方法,即向某个对象发送了某个消息。因此Objective-C的[instance method]调用会被编译器转换成C语言API objc_msgSend的调用。
以下消息转发机制原理图从官方文档翻译而来:

消息转发机制示例图
如上图所示,Objective-C的消息转发流程中,在当前对象方法列表中找不到方法的实现时,运行时环境会依次进行三个阶段的查找
第一阶段
对象在收到无法处理的消息时,首先调用所属类的下列类方法。
+(BOOL)resolveInstanceMethod:(SEL)sel{
//默认返回NO
return NO;
}
+(BOOL)resolveClassMethod:(SEL)sel{
//默认返回NO
return NO;
}
如果在其中找到了方法的实现,则进行消息处理;否则就进入第二阶段。
第二阶段
在当前类里找不到该方法的实现时,运行时系统尝试更换调用的对象,会调用如下方法
-(id)forwardingTargetForSelector:(SEL)aSelector{}
如果在该方法中还找不到方法的实现,就进入第三阶段。
第三阶段
到这里是最后一个阶段,通过创建NSInvocation实例,将与未处理的消息有关的细节封装起来,运行时系统调用的接口为
-(void)forwardInvocation:(NSInvocation *)anInvocation{}
该方法会沿着类的继承链一直往上调用,直至在NSObject的该方法中抛出doesNotRecognizeSelector:异常。
2、函数指针
SEL是Objective-C语言的方法选择器,也就是selector的指针。再往底层去,每个方法SEL还对应着一个IMP函数指针,指向方法实现的首地址。官方API为:
typedef id (*IMP)(id, SEL, ...);
有了它就可以直接执行IMP指向的函数(方法)了。
以上介绍了理论基础,下面介绍下基于Objective-C语言动态运行时的方法拦截(Hook)操作。
Hook原理

Hook步骤
Hook使用示例图

如图所示,开发者调用原有方法时,会转发到拦截的方法里,因交换了原方法与拦截方法的入口地址(IMP),在拦截方法执行结束时能调回原方法,对原有业务没有影响。
Hook回调函数
难点:不是类的实例方式,不能用Category特性。
优化前:全工程扫描,再拦截。
缺点:只能在主线程中操作,扫描文件数和消耗的时间与App的规模大小成正比。

回调函数拦截优化
步骤:

优化后
1、延迟拦截:在方法被调用时才被拦截而且只拦截一次
2、SDK的启动操作跟App的业务和规模无关,对App的影响降到最低
崩溃解码实践
目的:了解iOS崩溃解码原理



崩溃解码步骤
dwarfdum-e--debug-info 包含全部类和方法的代码和地址映射信息->信息文件
dwarfdum-e--debug-line 包含全部类的代码行号和地址映射信息->行号文件
虚拟内存偏移量:
0x00000001000238d8

dSYM文件解析命令
H5页面监控原理
以上介绍了系统原生接口的数据采集原理,下面介绍下H5页面数据采集实现原理。
H5页面数据采集通过自动往H5页面注入JS代码实现。流程如下图所示

H5页面注入JS代码示意图
如上图所示,在UIWebView时代,通过NSURLProtocol协议簇的接口,拦截到加载页面数据的接口,获取到H5数据块,检测数据块中的
标签,将JS代码注入到中。这样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资讯,了解云智慧开源社区其他项目开源情况!
