作者简介
刘徐兵(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步骤
- 使用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数据块,检测数据块中的标签,将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),备注:飞鱼,加入开发者交流群。
也可联系我们了解云智慧AIOps资讯,了解云智慧开源社区其他项目开源情况!