mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
update: 动态库、静态库的编译链接细节
This commit is contained in:
@@ -10,7 +10,7 @@ App 的包大小做优化的目的就是为了节省用户流量,提高用户
|
||||
|
||||
|
||||
|
||||
App 瘦身一般指的是安装包(IPA),主要由可执行文件、资源组成。
|
||||
App 瘦身一般指的是安装包(IPA),主要由**可执行文件、资源组成**。
|
||||
|
||||
对于产物的分析,可以查看可执行文件的具体组成。
|
||||
|
||||
@@ -32,7 +32,7 @@ App Thinning 是苹果公司推出的一项改善 App 下载进程的新技术
|
||||
|
||||
### 1.1 Slicing
|
||||
|
||||

|
||||

|
||||
|
||||
当向 App Store Connect 上传 .ipa 后,App Store Connect 构建过程中,会自动分割该 App,创建特定的变体(variant)以适配不同设备。然后用户从 App Store 中下载到的安装包,即这个特定的变体,这一过程叫做 Slicing。
|
||||
|
||||
@@ -42,7 +42,7 @@ App Thinning 是苹果公司推出的一项改善 App 下载进程的新技术
|
||||
|
||||
其中,2x 和 3x 的细分,要求图片在 **Assets** 中管理。Bundle 内的则会同时包含。
|
||||
|
||||

|
||||

|
||||
|
||||
### 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 来进行符号化
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
那么 Bitcode 会对 App Thining 有什么作用?
|
||||
|
||||
@@ -83,7 +83,7 @@ Bitcode 是一种程序`中间码`。包含 Bitcode 配置的程序将会在 App
|
||||
|
||||
on-Demand Resource 即一部分图片可以被放置在苹果的服务器上,不随着 App 的下载而下载,直到用户真正进入到某个页面时才下载这些资源文件。
|
||||
|
||||

|
||||

|
||||
|
||||
应用场景:相机应用的贴纸或者滤镜、关卡游戏等
|
||||
|
||||
@@ -101,7 +101,7 @@ on-Demand Resource 即一部分图片可以被放置在苹果的服务器上,
|
||||
|
||||
包体积,评判标准是以 App Store 上看到的为准。但是上传到 App Store Connect 处理完后,会自动帮我们生成具体设备上看到的大小。如下:
|
||||
|
||||

|
||||

|
||||
|
||||
这其中:又可以分为2类: Universal 和具体设备
|
||||
Universal 指通用设备,即未应用 App slicing 优化,同时包含了所有架构、资源。所以包体积会比较大
|
||||
@@ -245,13 +245,13 @@ self.imageView.image = images.lastObject;
|
||||
</details>
|
||||
|
||||
Timeprofile-imageNamedFromAssets
|
||||

|
||||

|
||||
|
||||
TimeProfile-imageWithContentsOfFile
|
||||

|
||||

|
||||
|
||||
Timeprofile-UIImageNamedFromFolder
|
||||

|
||||

|
||||
|
||||
Images.xcassets :
|
||||
|
||||
@@ -276,7 +276,7 @@ CocoPods 中两种资源引用方式介绍下:
|
||||
|
||||
说说项目中的情况吧:在工程中之前是通过 resource_bundles 引用资源的。资源是放在 Resources 目录下的图片引用。查询资料后说「如果图片资源放到 .xcasset 里面 Xcode 会帮我们自动优化、可以使用 Slicing 等(这里不仅仅指的是 resource_bundle 下的 xcassets」。所以动手将各个 Pod 库里面的图片全都通过 Assets Catalog 的方式进行处理。
|
||||
|
||||

|
||||

|
||||
|
||||
步骤:
|
||||
|
||||
@@ -433,14 +433,51 @@ Symbols Hidden by Default 会把所有符号都定义成”private extern”,
|
||||
|
||||
在 iOS微信安装包瘦身 一文中,有提到:
|
||||
|
||||
> 去掉异常支持,Enable C++ Exceptions和Enable 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++ Exceptions 和 Enable 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。
|
||||

|
||||
|
||||
<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 文件了。
|
||||

|
||||

|
||||
|
||||
产出的 LinkMap 阅读起来比较累,github 有个[可视化项目](https://github.com/jayden320/LinkMap) 用来查看 LinkMap 文件。
|
||||
|
||||
#### 3.2.1 基于 clang 扫描
|
||||
|
||||
@@ -499,11 +541,11 @@ LinkMap 文件分为3部分:Object File、Section、Symbols。
|
||||
|
||||
上面我们得知可以通过 LinkMap 统计出所有的类和方法,还可以清晰地看到代码所占包大小的具体分布,进而有针对性地进行代码优化。
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
得到了代码的全集信息后,我们还需要找到已经使用过的方法和类,这样才可以获取差集,找到无用代码。所以接下来就谈谈如何通过 Mach-O 取到使用过的类和方法。
|
||||
|
||||
@@ -520,7 +562,7 @@ Objective-C 中的方法都会通过 **objc_msgSend** 来调用,而 objc_msgSe
|
||||
|
||||
前置条件:先运行项目,在生成的 Products 目录下的 BridgeLabiPhone.app 解压,取出对应的和工程同名的 BridgeLabiPhone。然后运行上面的 Github 项目。可以看到运行了一个 Mac App。点击顶部的菜单栏里面的 File->Open。选择电脑上的 BridgeLabiPhone.app 选择里面的 BridgeLabiPhone。见下图
|
||||
|
||||

|
||||

|
||||
|
||||
由于 Objective-C 是一门动态语言,所以检测出的结果仍旧需要我们2次确认。
|
||||
|
||||
@@ -534,7 +576,7 @@ Objective-C 中的方法都会通过 **objc_msgSend** 来调用,而 objc_msgSe
|
||||
|
||||
AppCode 提供了 Inspect Code 来诊断代码,其中含有查找无用代码的功能。它可以帮助我们查找出 AppCode 中无用的类、无用的方法甚至是无用的 import ,但是无法扫描通过字符串拼接方式来创建的类和调用的方法,所以说还是上面所说的 基于源码扫描 更加准确和安全。
|
||||
|
||||

|
||||

|
||||
|
||||
说明: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 中。
|
||||
|
||||
最后的一个对比效果图:
|
||||

|
||||

|
||||
|
||||
总结:瘦身技术常见操作就这些,但是维持应用包体积的瘦身却是一个观念,从日常开发到线上发布都需要有这个意识。这样当你在写代码的时候就会考虑同样一个效果,你的具体实现手段是怎么样的。比如为了一个稍微炫酷的效果就要引入一个很大的三方库,有了“瘦身”的意识,你很大可能就是自己动手撸一个代码。比如一些无用资源的管理方式、有用的图片资源的高效管理方式等等。有了意识,行动自然会往这个方面去靠。(😂大道理一套一套的。我也不想的,毕竟是playboy)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user