一、前言

百度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编译整体结构

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

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

△图 4-1

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

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

△图 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实践运用版别具有必定差异性,因而部分编译相关内容需求进行相关适配

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

△图 4-3

4.2 整体计划规划

针对一门程序语言的运用而言,如图4-4所示,包括两个层面,一个层面是声明,另一个层面是调用。声明类,协议,特点,办法,函数等等,一起声明的内容是为了被运用,所以相同声明的内容皆可调用,只不过是内部调用仍是公开调用问题。从技能视点而言,声明的一切内容 减去 被调用的声明内容,剩余的便是未被调用的内容,也便是咱们需求的 无用办法。当然技能层面的判别终究仍是要进行事务断定,由于有的归于根底能力对外供给,至于是否要删去则需求进一步讨论。本文首要讨论技能层面问题。

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

△图 4-4

从clang源码中能够知道声明和调用别离对应LLVM源码中的基类Decl 和 Expr,整体技能计划如下图 4-5所示,针对无用办法分为处理分为四层:

1.Basic 层:组装编译东西所需的编译参数 + 进行语法规矩匹配

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

3.通用数据层:经过Transformer层产出的数据进行分类存储,所存储数据包括了代码的一切数据,如针对特点,办法,协议等数据均进行了分类存储

4.事务使用层:针对通用数据层产出的存储数据进行事务剖析即可

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

△图 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

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

△图 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 也能够)。

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

△图 4-7

详细构建指令首要分两种,一个是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的官方文档。

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

△图 4-8

整体代码流程如图 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:clang.llvm.org/doxygen/gro…

[2]libtooling 官方文档:clang.llvm.org/docs/LibToo…

[3]LLVM源码:github.com/llvm/llvm-p…

引荐阅读:

根据异常上线场景的实时拦截与问题分发战略

极致优化 SSD 并行读调度

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

DeeTune:根据 eBPF 的百度网络框架规划与使用

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