update: 动态库、静态库的编译链接细节

This commit is contained in:
FantasticLBP
2025-06-23 01:18:55 +08:00
parent aca020701b
commit 1142064d28
129 changed files with 10932 additions and 2615 deletions

View File

@@ -10,7 +10,7 @@ App 的包大小做优化的目的就是为了节省用户流量,提高用户
App 瘦身一般指的是安装包IPA主要由可执行文件、资源组成。
App 瘦身一般指的是安装包IPA主要由**可执行文件、资源组成**
对于产物的分析,可以查看可执行文件的具体组成。
@@ -32,7 +32,7 @@ App Thinning 是苹果公司推出的一项改善 App 下载进程的新技术
### 1.1 Slicing
![Slicing](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-15-AppSlicing.jpeg)
![Slicing](./../assets/2018-11-15-AppSlicing.jpeg)
当向 App Store Connect 上传 .ipa 后App Store Connect 构建过程中,会自动分割该 App创建特定的变体variant以适配不同设备。然后用户从 App Store 中下载到的安装包,即这个特定的变体,这一过程叫做 Slicing。
@@ -42,7 +42,7 @@ App Thinning 是苹果公司推出的一项改善 App 下载进程的新技术
其中2x 和 3x 的细分,要求图片在 **Assets** 中管理。Bundle 内的则会同时包含。
![变体](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-15-AppVariant.jpeg)
![变体](./../assets/2018-11-15-AppVariant.jpeg)
### 1.2 Bitcode
@@ -66,9 +66,9 @@ Bitcode 是一种程序`中间码`。包含 Bitcode 配置的程序将会在 App
在上传到 App Store 时需勾选“Includ app symbols for your application...”。勾选之后 Apple 会自动生成对应的 dYSM然后可以在 Xcode -> Window -> Organizer 中,或者在 Apple Store Connect 中下载对应的 dYSM 来进行符号化
![App Connect-dYSM](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-15-AppConnectYSM.jpeg)
![App Connect-dYSM](./../assets/2018-11-15-AppConnectYSM.jpeg)
![Xcode-dYSM](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-15-XcodedYSM.jpeg)
![Xcode-dYSM](./../assets/2018-11-15-XcodedYSM.jpeg)
那么 Bitcode 会对 App Thining 有什么作用?
@@ -83,7 +83,7 @@ Bitcode 是一种程序`中间码`。包含 Bitcode 配置的程序将会在 App
on-Demand Resource 即一部分图片可以被放置在苹果的服务器上,不随着 App 的下载而下载,直到用户真正进入到某个页面时才下载这些资源文件。
![on-DemandResources](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-15-on-DemandResources.png)
![on-DemandResources](./../assets/2018-11-15-on-DemandResources.png)
应用场景:相机应用的贴纸或者滤镜、关卡游戏等
@@ -101,7 +101,7 @@ on-Demand Resource 即一部分图片可以被放置在苹果的服务器上,
包体积,评判标准是以 App Store 上看到的为准。但是上传到 App Store Connect 处理完后,会自动帮我们生成具体设备上看到的大小。如下:
![App Store 包大小](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2018-11-15-AppVolume.jpeg)
![App Store 包大小](./../assets/2018-11-15-AppVolume.jpeg)
这其中又可以分为2类 Universal 和具体设备
Universal 指通用设备,即未应用 App slicing 优化,同时包含了所有架构、资源。所以包体积会比较大
@@ -245,13 +245,13 @@ self.imageView.image = images.lastObject;
</details>
Timeprofile-imageNamedFromAssets
![Timeprofile-imageNamedFromAssets](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-07-Timeprofile-imageNamedFromAssets.png)
![Timeprofile-imageNamedFromAssets](./../assets/2019-05-07-Timeprofile-imageNamedFromAssets.png)
TimeProfile-imageWithContentsOfFile
![TimeProfile-imageWithContentsOfFile](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-07-TimeProfile-imageWithContentsOfFile.png)
![TimeProfile-imageWithContentsOfFile](./../assets/2019-05-07-TimeProfile-imageWithContentsOfFile.png)
Timeprofile-UIImageNamedFromFolder
![Timeprofile-UIImageNamedFromFolder](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-07-Timeprofile-UIImageNamedFromFolder.png)
![Timeprofile-UIImageNamedFromFolder](./../assets/2019-05-07-Timeprofile-UIImageNamedFromFolder.png)
Images.xcassets
@@ -276,7 +276,7 @@ CocoPods 中两种资源引用方式介绍下:
说说项目中的情况吧:在工程中之前是通过 resource_bundles 引用资源的。资源是放在 Resources 目录下的图片引用。查询资料后说「如果图片资源放到 .xcasset 里面 Xcode 会帮我们自动优化、可以使用 Slicing 等(这里不仅仅指的是 resource_bundle 下的 xcassets」。所以动手将各个 Pod 库里面的图片全都通过 Assets Catalog 的方式进行处理。
![Pod组件库图片处理前后对比](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-08-Cocopod-Assets.png)
![Pod组件库图片处理前后对比](./../assets/2019-05-08-Cocopod-Assets.png)
步骤:
@@ -433,14 +433,51 @@ Symbols Hidden by Default 会把所有符号都定义成”private extern”
在 iOS微信安装包瘦身 一文中,有提到:
> 去掉异常支持Enable C++ ExceptionsEnable Objective-C Exceptions设为NO并且Other C Flags添加-fno-exceptions可执行文件减少了27M其中__gcc_except_tab段减少了17.3M__text减少了9.7M,效果特别明显。可以对某些文件单独支持异常,编译选项加上-fexceptions即可。但有个问题假如ABC三个文件AC文件支持了异常B不支持如果C抛了异常,在模拟器下A还是能捕获异常不至于Crash但真机下捕获不了(有知道原因可以在下面留言:)。去掉异常后Appstore 后续几个版本 Crash 率没有明显上升。
> 去掉异常支持Enable C++ ExceptionsEnable Objective-C Exceptions 设为 NO并且 Other C Flags 添加 `-fno-exceptions`可执行文件减少了27M其中 __gcc_except_tab 段减少了17.3M__text 减少了 9.7M,效果特别明显。可以对某些文件单独支持异常,编译选项加上 `-fexceptions` 即可。但有个问题,假如 ABC 三个文件AC 文件支持了异常B 不支持,如果 C 抛了异常,在模拟器下 A 还是能捕获异常不至于 Crash但真机下捕获不了。去掉异常后Appstore 后续几个版本 Crash 率没有明显上升。
个人认为关键路径支持异常处理就好像启动时NSCoder读取setting配置文件得要支持捕获异常等等
个人认为关键路径支持异常处理就好,像启动时 NSCoder 读取 setting 配置文件得要支持捕获异常,等等
看这个优化效果,感觉发现了新大陆。关闭后验证.. 毫无感知,基本没什么变化。
可能和项目中用到比较少有关系。故保持开启状态。
潜在问题与解决方案
问题 1依赖异常的标准库组件失效
- 现象:如 `std::vector` 在内存不足时直接崩溃。
- 解决:
- 使用 `std::nothrow` 分配内存。
- 替换为无异常的容器实现(如自定义或第三方库)。
问题 2第三方库依赖异常
- 现象:链接时报错(如库中未定义异常相关符号)。
- 解决:
- 重新编译第三方库,确保其也启用 `-fno-exceptions`。
- 隔离异常代码,通过 C 接口封装调用。
问题 3代码中残留 `try`/`catch`
- 现象:编译错误 `error: exception handling disabled`。
- 解决:
- 全局搜索并删除所有异常处理代码。
- 使用宏或条件编译隔离异常代码(不推荐)。
替代方案:若需保留部分异常逻辑但优化性能,可考虑**局部禁用异常**:通过 `#pragma clang exception_behavior disable` 或函数级属性控制。
```c++
#pragma clang exception_behavior disable
void criticalFunction() {
// 此函数内禁用异常处理
}
#pragma clang exception_behavior enable
```
#### 3.1.8 Link-Time Optimization
Link-Time Optimization 是 LLVM 编译器的一个特性,用于在 link 中间代码时,对全局代码进行优化。这个优化是自动完成的,因此不需要修改现有的代码;这个优化也是高效的,因为可以在全局视角下优化代码。
@@ -464,6 +501,8 @@ Link-Time Optimization 是 LLVM 编译器的一个特性,用于在 link 中间
代码的优化,即通过删除无用类、无用方法、重复方法等,来达到可执行文件大小的减小。
而如何筛选出符合条件的无用类、方法则需要通过一些工具来完成fui
**编写 LLVM 插件检测处重复代码,未被调用的代码。**
扫描无用代码的基本思路都是查找已经使用的方法/类和所有的类/方法,然后从所有的类/方法当中剔除已经使用的方法/类剩下的基本都是无用的类/方法,但是由于 Objective-C 是动态语言,可以使用字符串来调用类和方法,所以检查结果一般都不是特别准确,需要二次确认。目前市面上的扫描的思路大致可以分为 3 种:
- 基于 Clang 扫描
@@ -480,7 +519,8 @@ Link-Time Optimization 是 LLVM 编译器的一个特性,用于在 link 中间
- 工程师确认后,删除即可
LinkMap 文件分为3部分Object File、Section、Symbols。
![LinkMap结构](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-06-LinkMap-Structure.png)
<img src="./../assets/2019-05-06-LinkMap-Structure.png" style="zoom:50%"/>
- Object File包含了代码工程的所有文件
- Section描述了代码段在生成的 Mach-O 里的偏移位置和大小
@@ -489,7 +529,9 @@ LinkMap 文件分为3部分Object File、Section、Symbols。
先说说如何快速找到方法和类的全集?
我们可以通过 **LinkMap** 来获得所有的代码类和方法的信息。获取 LinkMap 可以通过将 Build Setting 里面的 **Write Link Map File** 设置为 YES然后指定 **Path to Link Map File** 的路径就可以得到每次编译后的 LinkMap 文件了。
![Xcode中设置获取LinkMap](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-06-LinkMap-Xcode.png)
![c](./../assets/2019-05-06-LinkMap-Xcode.png)
产出的 LinkMap 阅读起来比较累github 有个[可视化项目](https://github.com/jayden320/LinkMap) 用来查看 LinkMap 文件。
#### 3.2.1 基于 clang 扫描
@@ -499,11 +541,11 @@ LinkMap 文件分为3部分Object File、Section、Symbols。
上面我们得知可以通过 LinkMap 统计出所有的类和方法,还可以清晰地看到代码所占包大小的具体分布,进而有针对性地进行代码优化。
![LinkMap-Object file](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-05-LinkMap-ObjectFile.png)
![LinkMap-Object file](./../assets/2019-05-05-LinkMap-ObjectFile.png)
![LinkMap-Sections](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-05-LinkMap-Sections.png)
![LinkMap-Sections](./../assets/2019-05-05-LinkMap-Sections.png)
![LinkMap-Symbols](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-05-LinkMap-Symbols.png)
![LinkMap-Symbols](./../assets/2019-05-05-LinkMap-Symbols.png)
得到了代码的全集信息后,我们还需要找到已经使用过的方法和类,这样才可以获取差集,找到无用代码。所以接下来就谈谈如何通过 Mach-O 取到使用过的类和方法。
@@ -520,7 +562,7 @@ Objective-C 中的方法都会通过 **objc_msgSend** 来调用,而 objc_msgSe
前置条件:先运行项目,在生成的 Products 目录下的 BridgeLabiPhone.app 解压,取出对应的和工程同名的 BridgeLabiPhone。然后运行上面的 Github 项目。可以看到运行了一个 Mac App。点击顶部的菜单栏里面的 File->Open。选择电脑上的 BridgeLabiPhone.app 选择里面的 BridgeLabiPhone。见下图
![Mach-O-inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-07-Mach-O-Inspect.png)
![Mach-O-inspect](./../assets/2019-05-07-Mach-O-Inspect.png)
由于 Objective-C 是一门动态语言所以检测出的结果仍旧需要我们2次确认。
@@ -534,7 +576,7 @@ Objective-C 中的方法都会通过 **objc_msgSend** 来调用,而 objc_msgSe
AppCode 提供了 Inspect Code 来诊断代码,其中含有查找无用代码的功能。它可以帮助我们查找出 AppCode 中无用的类、无用的方法甚至是无用的 import ,但是无法扫描通过字符串拼接方式来创建的类和调用的方法,所以说还是上面所说的 基于源码扫描 更加准确和安全。
![AppCode-code inspect](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-05-CodeClean.png)
![AppCode-code inspect](./../assets/2019-05-05-CodeClean.png)
说明AppCode检测出了实际上需要的大部分场景的问题但是由于 Objective-C 是一门动态性语言,所以 AppCode 检测出无用的方法等都需要工程师自己再次确认后删除。(在我们的工程中有一些和 H5 交互的桥接方法,因此 AppCode 视为 Unused Method但是你删除的话那就自己哭去吧 😭)。实际经验告诉我,使用 AppCode 的时候如果工程比较大,则整个 code inspect 会非常耗时(给你打个预防针哦,笔芯)
@@ -642,7 +684,7 @@ lipo create libWeiboSDK-armv7.a libWeiboSDK-arm64.a -output libWeiboSDK.device.a
DebuggingWith Arbitrary Record Formats 是 ELF 和 Mach-O 等文件格式中用来存储和处理调试信息的标准格式,.dSYM 文件中真正保存符号表数据的是 DWARF 文件。DWARF 文件中不同的数据都保存在相应的 section 中。
最后的一个对比效果图:
![瘦身效果图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-09-AppThinning-Comparation.png)
![瘦身效果图](./../assets/2019-05-09-AppThinning-Comparation.png)
总结瘦身技术常见操作就这些但是维持应用包体积的瘦身却是一个观念从日常开发到线上发布都需要有这个意识。这样当你在写代码的时候就会考虑同样一个效果你的具体实现手段是怎么样的。比如为了一个稍微炫酷的效果就要引入一个很大的三方库有了“瘦身”的意识你很大可能就是自己动手撸一个代码。比如一些无用资源的管理方式、有用的图片资源的高效管理方式等等。有了意识行动自然会往这个方面去靠。😂大道理一套一套的。我也不想的毕竟是playboy