百度APP iOS端包体积50M优化实践(六)无用方法清理

一、前言

百度APP包体积经过一期优化,如无用资源清理,无用类下线,Xcode编译相关优化,体积已经有了明显的减少。但是优化后APP包体积在iPhone11上仍有350M的空间占用。与此同时百度APP作为百度的旗舰APP,业务迭代非常多且迅速,体积优化和防劣化仍然是当前阶段的一个核心任务。因此百度APP开启了粒度更小,修复风险更高的无用方法清理相关工作。期望通过无用方法清理,有效降低百度APP的包体积,同时删除项目中的无用方法,冗余代码,提高代码的整洁度。

百度APP iOS端包体积优化实践系列文章回顾:

  • 《百度APP iOS端包体积50M优化实践(一)总览》

  • 《百度APP iOS端包体积50M优化实践(二) 图片优化》

  • 《百度APP iOS端包体积50M优化实践(三) 资源优化》

  • 《百度APP iOS端包体积50M优化实践(四) 代码优化》

  • 《百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践》

二、方案调研

针对无用方法清理,调研了各家厂商目前已公布的方案,主流方案基于Mach-O + LinkMap文件的分析,但是主要存在以下问题:

1.准确度低

2.针对系统方法需要手动过滤

3.针对load、initilize、attribute 相关调用无法识别

4.针对string反射调用无法识别,Target-Action 注册,Observer注册方法等无法识别

5.复杂语法场景下无法识别,如继承链中的方法调用,子类实现父类方法等场景

6.系统通知等场景

因为目前已公布方案存在如上不足,同时因为下线代码敏感度非常高,相关业务都很慎重。因此推动相关无用方法清理,识别准确度将非常重要,直接关系到相关业务下线无用代码的积极性,因此弃用了上述方案。

三、方案选择

针对第二部分方案不足之处进行分析,可以看到其准确度低的核心问题是,针对产物进行分析,拿不到所有需要的信息,或者说还没有发现有效的手段去获取所期望获得的信息。而想要解决上面提到的问题,最佳途径就是获取到尽可能多的代码信息。既然从产物回溯不到所需要的,那么就可以考虑从源头也就是源码层面找到我们所需要的详细信息。

源码肯定包含了所有的信息,但是针对源码如何分析呢,主要有以下三种:

  • 通过脚本直接分析源码

需要匹配源码的所有语法规则,才能够针对源码进行有效的分析,相当于写一个源码解析器,所以这个方案放弃

  • 通过脚本直接分析AST(抽象语法树)

编译过程中产生的抽象语法树(AST)包含了需要的所有信息,并且clang也提供了命令行,使用该命令行能够直接获取到AST数据。但是clang 命令获取AST数据是以单个类为维度的,类与类之间的关系很难获取到,如继承关系,分类和主类的关系是无法获取的,所以这个方案同样放弃

  • 通过libtooling 和 Swift Compiler自建编译套件分析AST (Swift相关会在下一篇文章中介绍)

既然通过clang命令生成的AST产物分析仍然不能满足需求,那么直接介入编译过程,从编译内部生成AST过程中获取需要的信息,最终这个方案被采用。通过libtooling 和 Swift Compiler自建编译套件针对AST进行分析,获取所需要的所有信息。

四、方案设计

如上所述百度APP最终采用了libtooling 和 Swift Compiler 静态分析方案,那么下面就从原理和实现层面分别进行阐述。

4.1 编译流程简介

4.1.1 Xcode编译总体结构

本节先简单聊一下编译器的结构,编译流程,和静态分析是什么?

图片

△图 4-1

如图4-1 所示 LLVM 采用如上三段结构(Three Phase Design),分别是编译前端(Frontend),编译优化模块,编译器后端(Backend)。那么这三段结构如何对应到Xcode呢,如图4-2所示:

图片

△图 4-2

日常使用Xcode编译时,Xcode调用了两个编译器前端,分别为Clang 和 Swift,通过两个编译器前端构建出通用的编译产物,然后统一经过LLVM后端编译器进行目标文件生成。

通过Xcode的编译log,可以看到针对Objective-C,C, C++ 使用了clang进行编译,针对上述三种不同语言分别用不同编译参数控制:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang

针对swift 文件则采用了swift编译器进行了编译:

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend

针对这两个可执行文件大家可以自行解包Xcode,进行命令行调用,也可以通过其 --help指令查看其支持哪些编译参数或者功能。Xcode 内部编译器实际上是苹果对LLVM 和 Swift 开源版本的定制化版本, 和开源版本有一定的差异性。

4.1.2 Clang 和 Swift 编译流程

如下图所示Clang 和 Swift 前端编译流程,可以看到Swift 编译处理流程多了SIL部分,实际里面还有一个SIL Guaranteed Transformations,当然SIL部分不是重点。从图4-3中可以看到Clang 和 Swift compiler 都会生成AST 且发现AST中包含了我们需要的绝大部分信息,并且Clang 和 Swift Compiler 也暴露了相关获取AST信息的接口,那么剩下的工作只有四点:

1.搭建编译套件工程,确保它正常run起来

2.获取AST,并且根据Objective-C 或者 C,C++的语法特性获取所需要的数据

3.针对获取的数据进行业务分析处理

4.开源版本LLVM和Xcode实际使用版本具有一定差异性,因此部分编译相关内容需要进行相关适配

图片

△图 4-3

4.2 总体方案设计

针对一门程序语言的使用而言,如图4-4所示,包含两个层面,一个层面是声明,另一个层面是调用。声明类,协议,属性,方法,函数等等,同时声明的内容是为了被使用,所以同样声明的内容皆可调用,只不过是内部调用还是公开调用问题。从技术角度而言,声明的所有内容 减去 被调用的声明内容,剩下的就是未被调用的内容,也就是我们需要的 无用方法。当然技术层面的判别最终还是要进行业务判定,因为有的属于基础能力对外提供,至于是否要删除则需要进一步探讨。本文主要探讨技术层面问题。

图片

△图 4-4

从clang源码中可以知道声明和调用分别对应LLVM源码中的基类Decl 和 Expr,整体技术方案如下图 4-5所示,针对无用方法分为处理分为四层:

1.Basic 层:组装编译工具所需的编译参数 + 进行语法规则匹配

2.Transformer层:针对语法规则匹配数据进行转换,转换通用型数据格式

3.通用数据层:通过Transformer层产出的数据进行分类存储,所存储数据包含了代码的所有数据,如针对属性,方法,协议等数据均进行了分类存储

4.业务应用层:针对通用数据层产出的存储数据进行业务分析即可

图片

△图 4-5

4.3 详细方案实现

4.3.1 Objective-C 编译工具搭建

编译工具的呈现形式是一个类似Xcode自带clang的可执行文件,如图4-6 红框所示内容。

/Users/UserName/Documents/XcodeEdition/Xcode14.2/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang

图片

△图 4-6

简单来说通过源码构建的编译工具具有Xcode clang 的部分功能,利用其编译过程中产生的AST对象进行抽象语法树分析,获取到所需要的编程语言的所有语法信息。

4.3.1.1 LLVM 源码构建

编译工具的搭建需要依赖LLVM提供的静态库或动态库,这些库通过自己构建LLVM源码来获得。可以从github获取LLVM源码路径,进入LLVM github界面后有可能会困惑需要构建哪个分支或者tag的代码呢,哪个版本和Xcode使用的clang是对应的?目前Xcode的版本是 14.2 或者 14.3 ,使用命令 clang --version 可以看到Xcode用到的是clang 14,因此构建了release/14.x(没有找到对应关系,推理得出),构建成功后执行构建的clang --version 会发现开源版本clang 和 Xcode的小版本号是不一样的,这是因为Xcode 用的clang 苹果会基于开源代码进行定制,这从Xcode中clang 的依赖库或头文件数量。另外从编译log也可以看到,Xcode clang支持的部分参数,开源clang是不支持的。尽管苹果有一些定制,但是总体影响有限。因此也不必过于在意小版本号是否一致。(初步验证了一下构建最新的release/16.x clang16 也可以)。

图片

△图 4-6

具体构建命令主要分两种,一个是Ninja 构建方式,一个是Xcode方式,需要Xcode调试源码可以选择Xcode模式,但是最终集成到编译工具中的静态库,一定要构建成Release模式,这样工具体积会降到最低,一些警告类异常也会被屏蔽掉。可以参照LLVM 开源库中的start guide 构建过程进行构建,其中涉及的组装命令可以自行拼接也可以用下面的命令:

构建过程
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build (这个build文件夹可以自行命名,不固定。针对不同目标可以创建不同文件夹进行不同构建,如 mkdir ninjaBuild 或 mkdir xcodeBuild)
cd build (or cd xcodeBuild)
cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Release ../llvm
cmake --build .

编译Xcode版本,Ninja替换为Xcode即可。

4.3.1.2 工程搭建

LLVM提供了两种工具 libclang 和 libtooling,百度APP采用的是 libtooling,其异同点如下所示:

  • libclang:(网络资料,未实测)

    1.提供稳定的 C 接口,具有遍历语法树,获取 Token,代码补全等能力。

    2.接口稳定,clang 版本更新对齐影响不大

    3.libclang 不能获取到 AST 的所有信息

  • libtooling:(实测)

    1.提供 C++ 接口,产出的工具不依赖于编译器,可作为独立命令使用

    2.接口不稳定,AST 有升级需要更新相关依赖库

    3.libtooling 可以获得 AST 的所有信息

最终选择 libtooling 形式,核心原因就是 libtooling 可以获取 AST 的所有信息,同时能够不依赖于Xcode 独立运行。工程的搭建本身并不复杂,还是属于API 使用层面,可以直接参照 libtooling的官方文档。

图片

△图 4-6

总体代码流程如图 4-8所示,主要核心点是五个部分:

  • 参数解析

  • 创建 ClangTool 参照LLVM源码 ClangTooling -> Tooling.h Line309

  • 创建 ASTFrontendAction,用于获取 AST 数据,创建 ASTConsumer 和 进行 ASTMatcher 绑定

  • 针对 ASTMatcher 匹配项进行各语法规则匹配

  • 根据匹配数据进行数据过滤及业务处理

4.3.1.3 数据存储结构设计

数据存储结构采用 json 格式,以下为基础数据格式示例,可以根据实际需求拓展:

"objc(协议or类)@类名(类方法or实例方法)@方法名称":{
"identifier":"objc(协议or类)@类名(类方法or实例方法)@方法名称",
"isInstance":true,
"kind":16,
"location":{
"col":36,
"filename":"文件名称",
"line":147},
"name":"方法名称",
"paramters":"参数",
"returnType":"返回值类型",
"sourceCode":"源码"
}
{"declaration":{"identifier":"objc(协议or类)@类名(类方法or实例方法)@方法名称","isInstance":true,"kind":16,"location":{            "col":列数,"filename":"声明所在类名",            "line":行数        },"name":"方法名称","paramters":"参数名称","returnType":"返回值类型","sourceCode":"源代码"    },"kind":1,"location":{"col":5,"filename":"当前所在文件名","line":15    }}

五、遇到的问题及解决方案

1. 属性调用识别问题

针对 Objective-C 的属性,在编译后对应两个方法 get 和 set 一个是 ivar,调用方有可能只调用 get 或者 set 或者 ivar,所以当只发生一种调用时,就算这个属性被调用,当前属性不属于无用方法。需要在结果中把另外两个方法剥离。

2. 提取方法内容时同样需要对头文件进行提取

方法的实现不一定只在.m 文件中,如C++的头文件是可以进行方法实现的,Objective-C 的.h 文件 通过 inline 实现一些方法,在语法上也是可行的。所以进行方法提取时候关注实现文件,同时也要关注头文件。

3. 针对继承问题

子类实现父类方法等场景,在识别方法时,全部回溯其父类,以其父类名称作为 上文数据结构中 identifier 中类名部分,这样所有的方法都可以和其声明类匹配。

4. 过滤系统方法调用

LLVM提供了接口判断当前方法是否属于系统类。

5. 过滤业务类实现系统方法问题

针对当前类中所有的方法均在当前类 和 回溯其继承链条中的父类, 分别判断其是否属于系统方法,如果属于系统方法则直接过滤掉。

6. 针对协议方法的实现,目前还没有有效手段识别,当前方案是直接过滤掉协议方法,所有协议方法均视为已经调用

在提取方法时,判断当前interface 遵循了哪些协议,遍历协议中的方法,判断其是否为协议方法,是则标记为已调用。

7. 子类实现父类协议问题

回溯当前类的继承链条,在继承链条中判断遍历其所遵循的协议,判断其是否为协议方法。

8. 正常业务实现协议,应该明确标注当前类遵循了协议 如 interface ,但是实际场景中有很多代码在实现协议时并没有标注conformprotocol 这样就对协议方法的判断产生影响,如 6.7方案均失效了

如果组件中少量这种问题,当推动相关方修复此问题,需要明确遵循协议。但是如果有的组件这种场景较多,短期不会修复所有,那么就需要进行临时性适配。针对这类组件收集其当前组件所声明的协议的所有协议方法,用收集的协议方法和当前组件提取的所有声明做差集,存在误伤的可能,但结果是置信的(组件只是一个维度,也可以针对其关联组件进行相关处理,因为有时他实现的组件不一定在当前组件内,这就需要当前组件的依赖关系了)。

无用方法case很多,列举部分供大家参考。

六、总结

这项技术实际上在百度APP早已经应用,因为笔者之前负责百度APP的接口变更审核,组件完整性校验,隐私合规调用链分析等均是依赖于此项技术,无用方法识别只是笔者在做体积优化时想到的其功能的一个延展。当然如上描述的技术问题,细节处理无用方法显然更细腻,case更多。后续文章会针对Swift无用方法分析,接口变更审核,组件完整性校验,隐私合规调用链分析等一一作出介绍。

** ——END——**

参考资料:

[1]libclang:https://clang.llvm.org/doxygen/group__CINDEX.html

[2]libtooling 官方文档:https://clang.llvm.org/docs/LibTooling.html

[3]LLVM源码:https://github.com/llvm/llvm-project

推荐阅读:

基于异常上线场景的实时拦截与问题分发策略

极致优化 SSD 并行读调度

AI文本创作在百度App发文的实践

DeeTune:基于 eBPF 的百度网络框架设计与应用

百度自研高性能ANN检索引擎,开源了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/84559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysq 主从同步错误之 Error_code 1032 handler error HA_ERR_KEY_NOT_FOUND

错误说明: MySQL主从同步的1032错误,一般是指要更改的数据不存在,SQL_THREAD提取的日志无法应用故报错,造成同步失败 (Update、Delete、Insert一条已经delete的数据)。 1032的错误本身对数据一致性没什么影…

基于SSM的四六级报名与成绩查询系统的设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

【2】贪心算法-综述

前言 从前,有一个很穷的人救了一条蛇的命,蛇为了报答他的救命之 恩,于是就让这个人提出要求,满足他的愿望。这个人一开始只要求简 单的衣食,蛇都满足了他的愿望,后来慢慢地贪欲升起,要求做官&am…

selenium+python实现基本自动化测试

安装selenium 打开命令控制符输入:pip install -U selenium 火狐浏览器安装firebug:www.firebug.com,调试所有网站语言,调试功能 Selenium IDE 是嵌入到Firefox 浏览器中的一个插件,实现简单的浏览器操 作的录制与回…

屏幕分辨率dpi解析(adb 调试查看)

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 ro.sf.lcd_density属性指定了这个机型使用的dpi是多少,dpi全称是dots per inch,对角线每英寸的像素点的个数。 密度 ldpi mdpi hdpi xhdpi xxhdpi 分辨率 240x320 320x480 480x800 7…

Cannot find module ‘core-js/modules/es6.regexp.constructor‘

npm run dev 之后报如下错误 解决方法:npm install core-js2 如果超时或者下载时间慢可以尝试 用cnpm install core-js2

软件工程知识总结梳理

🔥🔥宏夏Coding网站,致力于为编程学习者、互联网求职者提供最需要的内容!网站内容包括求职秘籍,葵花宝典(学习笔记),资源推荐等内容。在线阅读:https://hongxiac.com&…

Nodejs 第十六章(ffmpeg)

FFmpeg 是一个开源的跨平台多媒体处理工具,可以用于处理音频、视频和多媒体流。它提供了一组强大的命令行工具和库,可以进行视频转码、视频剪辑、音频提取、音视频合并、流媒体传输等操作。 FFmpeg 的主要功能和特性: 格式转换:…

Network: use `--host` to expose

vite 启动项目提示 Network: use --host to expose 同事不能通过本地IP地址访问项目 解决方案:package.json中启动命令配置本地IP地址 vite --host 192.168.200.252

Twitter图片数据优化的细节

Twitter个人数据优化:吸引更多关注和互动 头像照片在Twitter上,头像照片是最快识别一个账号的方法之一。因此,请务必使用公司的标志或与品牌相关的图片。建议尺寸为400x400像素。 为了建立强大的品牌形象和一致性,强烈建议在所有…

虹科教您 | 可实现带宽计量和延迟计算的时间敏感网络测试工具RELY-TSN-LAB操作指南与基本功能测试

1. RELY-TSN-LAB产品概述 时间敏感网络(TSN)能够合并OT和IT世界,这将是真正确保互操作性和标准化的创新性技术。这项技术的有效开发将显著降低设备成本、维护、先进分析服务的无缝集成以及减少对单个供应商的依赖。为了在这些网络中实现确定性,需要控制…

Kotlin Android中错误及异常处理最佳实践

Kotlin Android中错误及异常处理最佳实践 Kotlin在Android开发中的错误处理机制以及其优势 Kotlin具有强大的错误处理功能:Kotlin提供了强大的错误处理功能,使处理错误变得简洁而直接。这个特性帮助开发人员快速识别和解决错误,减少了调试代…

12:STM32---RTC实时时钟

目录 一:时间相关 1:Unix时间戳 2: UTC/GMT 3:时间戳转化 二:BKP 1:简历 2:基本结构 三: RTC 1:简历 2: 框图 3:RTC基本结构 4:RTC操作注意 四:案例 A:读写备份寄存器 1:连接图 2: 步骤 3: 代码 B:实时时钟 1:连接图 2:函数介绍 3:代码 一:时间相关 1:Un…

Linux -- 使用多张gpu卡进行深度学习任务(以tensorflow为例)

在linux系统上进行多gpu卡的深度学习任务 确保已安装最新的 TensorFlow GPU 版本。 import tensorflow as tf print("Num GPUs Available: ", len(tf.config.list_physical_devices(GPU)))1、确保你已经正确安装了tensorflow和相关的GPU驱动,这里可以通…

【数据结构练习】链表面试题集锦二

目录 前言: 1.链表分割 2.相交链表 3.环形链表 4.环形链表 II 前言: 数据结构想要学的好,刷题少不了,我们不仅要多刷题,还要刷好题!为此我开启了一个必做好题锦集的系列,每篇大约5题左右。此…

“高级前端开发技术探索路由的使用及Node安装使用“

目录 引言1. Vue路由的使用2. VueNode.js的安装使用总结 引言 在当今互联网时代,前端开发技术日新月异,不断涌现出各种新的框架和工具。作为一名前端开发者,我们需要不断学习和探索新的技术,以提升自己的开发能力。本文将深入探讨…

【C# Programming】值类型、良构类型

值类型 1、值类型 值类型的变量直接包含值。换言之, 变量引用的位置就是值内存中实际存储的位置。 2、引用类型 引用类型的变量存储的是对一个对象实例的引用(通常为内存地址)。 复制引用类型的值时,复制的只是引用。这个引用非常小&#xf…

前后端分离毕设项目之产业园区智慧公寓管理系统设计与实现(内含源码+文档+教程)

博主介绍:✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久,选择我们就是选择放心、选择安心毕业✌ 🍅由于篇幅限制,想要获取完整文章或者源码,或者代做&am…

短视频抖音账号矩阵系统源码开发者自研(四)

抖音是一款备受欢迎的短视频APP,拥有数亿的用户,其中包括了大量的粉丝。为了让更多的人能够发现和观看到你的视频,抖音SEO是必不可少的一环,特别是对于拥有企业或个人品牌的用户来说。在这个过程中,抖音SEO源码的开源部…

SQL注入脚本编写

文章目录 布尔盲注脚本延时注入脚本 安装xampp,在conf目录下修改它的http配置文件,如下,找到配置文件: 修改配置文件中的默认主页,让xampp能访问phpstudy的www目录,因为xampp的响应速度比phpstudy快得多&am…