Swift编译器无法移除未使用公共代码?如何实现移除?
关于Swift SPM模块中未使用公共代码的推断验证与移除方案
你的推断是否正确?
你的推断部分正确,但需要补充关键细节:
- Swift编译器的全模块优化(WMO)和死代码剥离确实会安全移除未使用的
internal符号:因为internal作用域仅限模块内部,编译器可以通过全模块分析确定符号未被任何代码引用,因此会在编译或链接阶段剥离。 - 对于
public符号,编译器默认会保守处理:public符号被设计为模块对外暴露的API,编译器无法确定是否会被当前依赖关系之外的代码(比如动态调用、未来新增的依赖模块)使用,因此即使当前依赖模块未用到,也不会轻易移除——这就是你观察到public未使用函数仍占用二进制空间的原因。
如何让编译器移除未使用的公共代码?
可以通过以下几种方案实现,根据你的场景选择最合适的方式:
1. 使用@_spi标记非对外API的公共函数
如果该public函数只是模块间内部协作使用,并非对外公开的正式API,可以用@_spi标记,将其标记为“SPI(Stable Public Interface)”符号,编译器会将其视为半内部符号,在全模块优化下可以被安全剥离:
@_spi(Internal) public func unusedPublicFunction() { // 函数实现 }
注意:
@_spi是Swift的非官方特性,虽然在SPM场景中广泛使用,但未来版本可能有变化,不建议用于对外的正式SDK。
2. 确保全模块优化与死代码剥离已启用
需要同时开启两个关键编译/链接选项,且确保依赖目标为静态框架(动态框架的public符号会被保留供动态链接):
- 在Xcode中,对两个目标分别设置:
- Swift Compiler - Code Generation -> Compilation Mode:设为
Whole Module - Linking -> Dead Code Stripping:设为
Yes
- Swift Compiler - Code Generation -> Compilation Mode:设为
- 如果使用SPM,可以在
Package.swift中配置:targets: [ .target( name: "YourDependencyTarget", swiftSettings: [ .unsafeFlags(["-whole-module-optimization"]) ], linkerSettings: [ .linkedFlag("-dead_strip") ] ), .target( name: "YourMainTarget", dependencies: ["YourDependencyTarget"], swiftSettings: [ .unsafeFlags(["-whole-module-optimization"]) ] ) ]
3. 谨慎使用@inlinable标记小函数
如果未使用的public函数逻辑简单、体积小,可以标记为@inlinable:
@inlinable public func unusedPublicFunction() { // 简单实现 }
这样主模块编译时会将函数体内联到调用处,依赖模块中的原函数因无其他引用会被剥离。但注意@inlinable会将函数体暴露给调用模块,可能破坏ABI稳定性,仅适合无状态的工具类小函数。
4. 拆分模块隔离非公开代码
将不需要对外暴露的代码拆分到一个internal子模块中,仅保留真正需要对外的API在主公共模块。这样子模块中的internal符号可以被正常剥离,主模块的public API也保持简洁。
内容的提问来源于stack exchange,提问作者Maxim Zenkov
相关产品推荐
相关产品推荐

