Xcode未剥离静态框架未使用符号:死代码剥离疑问求助
问题重现
我正在学习Apple的**死代码剥离(Dead-code stripping)**特性,通过SPM创建Demo并在三种场景下测试应用体积:
- 空项目,启用App Thinning后导出:未压缩体积148KB,可执行文件88KB
- 通过SPM添加Pulse & Pulse UI依赖,但源码中未导入/使用,启用App Thinning后导出:未压缩体积2.7MB,可执行文件2.7MB
- 导入并使用Pulse & Pulse UI,启用App Thinning后导出:未压缩体积2.7MB,可执行文件2.7MB
预期场景2的体积应与场景1接近,但实际却和场景3一致。已尝试的操作:
- 使用Xcode 14,
Enable Bitcode = NO - 场景2下用
dwarf-dump查看dSYM文件,发现Pulse相关符号仍存在 - 按博客配置死代码剥离无效果
- 使用Swift 5.7.2、Pulse最新main分支,Pulse静态链接至可执行文件
- 运行
xcrun dyld_info -exports检查,输出显示Pulse已被剥离
可能原因分析
静态链接的特性限制
Swift包默认采用静态链接,链接器会将依赖库中包含被引用符号的整个目标文件(.o)都链接到可执行文件中。如果Pulse的代码中存在全局变量初始化、@objc注册的类,或者带有__attribute__((used))/@_used属性的符号,即使你没直接使用,链接器也会认为这些代码需要保留,无法剥离。依赖包本身的死代码剥离配置缺失
如果Pulse的Package.swift中没有为target设置enableDeadCodeStripping: true,即使你的项目开启了死代码剥离,依赖库的代码也可能无法被正确标记为可剥离。部分第三方库为了兼容性会默认关闭该配置。dSYM符号的误导
dSYM文件是用于调试的符号集合,会保留所有原始符号信息,不管可执行文件中的代码是否被剥离。你看到dSYM中有Pulse符号,不代表可执行文件中还存在对应的代码,dyld_info -exports的结果才是可执行文件导出符号的真实状态。Xcode剥离配置未完全生效
仅开启Dead Code Stripping可能不够,还需要确保以下配置正确:
Deployment Postprocessing设置为YESStrip Style设置为All SymbolsStrip Debug Symbols During Copy设置为YES
解决与验证步骤
- 检查并修改Pulse的死代码剥离配置
在Pulse的Package.swift中,为对应的target添加死代码剥离配置:
.target( name: "Pulse", dependencies: [], settings: [ .enableDeadCodeStripping(true) ] ), .target( name: "PulseUI", dependencies: ["Pulse"], settings: [ .enableDeadCodeStripping(true) ] )
- 完善项目的剥离相关配置
在Xcode项目的Build Settings中,确认:
Dead Code Stripping=YESDeployment Postprocessing=YESStrip Style=All SymbolsStrip Debug Symbols During Copy=YES
- 验证可执行文件中的真实符号
用nm命令直接检查可执行文件中的符号:
nm -m /path/to/your/app/executable | grep -i pulse
如果输出为空或只有少量无关符号,说明代码已被剥离,体积增加可能是Swift标准库的额外部分或其他编译产物导致;如果有大量Pulse相关符号,说明确实有未被剥离的代码。
- 尝试动态链接依赖
在SPM添加依赖时指定动态链接类型:
.target( name: "YourApp", dependencies: [ .product(name: "Pulse", package: "Pulse", type: .dynamic), .product(name: "PulseUI", package: "Pulse", type: .dynamic) ] )
动态库的死代码剥离逻辑更简单,未使用的动态库不会被打包进IPA。
总结
这并非Xcode的Bug,更多是对死代码剥离在静态链接场景下的认知偏差,或遗漏了依赖包及项目的关键配置。通过调整依赖包的死代码剥离设置、完善项目编译配置,可以实现未使用静态依赖的代码剥离。
内容的提问来源于stack exchange,提问作者Hoang Anh Tuan

