解决 swift-collections 1.3.0 在 Xcode 26.1 下的 Lifetime 特性编译错误
最近在 vibe coding 一款 macOS App,名叫 “Hassan”,具体做什么的暂且按下不表。项目在从 Xcode 16.4 升级到 26.1 后遇到了一个编译错误,花费几个小时研究明白后决定水一篇文章,整理一下这几个小时的收获。
问题描述
开发环境
先交代一些我的开发环境:
- macOS Tahoe 26.1 (25B78)
- Xcode 26.1 (17B55)
- Tuist 4.90.0
- swift-collections 1.3.0
问题表现
Hassan 使用了 Swift Collections 1.3.0。其在 Xcode 16.4 (16F6) 下编译一切正常,但切换到 Xcode 26.1 后,编译失败并报错:
/Users/rakuyo/Hassan/Tuist/.build/checkouts/swift-collections/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift:104:2 '@_lifetime' attribute is only valid when experimental feature Lifetimes is enabled
/Users/rakuyo/Hassan/Tuist/.build/checkouts/swift-collections/Sources/InternalCollectionsUtilities/autogenerated/LifetimeOverride.swift:107:6 A function cannot return a ~Escapable result
问题分析
根本原因
看了一下代码,发现报错的地方用了 #if compiler(>=6.2) 做条件编译:
- Xcode 16.4 的编译器版本小于 6.2,所以这部分代码根本不会编译
- Xcode 26.1 的编译器达到了 6.2,就触发了这部分代码的编译
swift-collections 的 Package.swift 文件
翻了一下 swift-collections 的源码,返现它有两个 Package.swift 文件:
- 默认的 Package.swift
- Swift 6.0 版本使用的 Package@swift-6.0.swift
关键的区别在于:
Package.swift文件中包含了.enableExperimentalFeature("Lifetimes")配置Package@swift-6.0.swift文件中没有这个配置
关于 @_lifetime 特性
@_lifetime 是 Swift 6.2 引入的实验性特性,用于处理生命周期依赖关系。虽然 6.2 里已经有了,但它还不是正式的语言特性,所以需要显式开启。
官方论坛里有讨论:Experimental support for lifetime dependencies in Swift 6.2 and beyond
解决问题
最初的思路
一开始,我认为问题出在 SPM 或 Tuist 错误地选择了 Package@swift-6.0.swift 文件。因为我的项目设置的是 Swift 5.9,Build Settings 里也是 5.9,我认为应该使用默认的 Package.swift(包含了 Lifetimes 特性的启用),这样就不会有编译错误。
深入调查
但是继续往下查,发现这事儿比想的复杂:
SPM 使用工具链的 Swift 版本
SPM 在下载依赖时,使用的是 工具链 的 Swift 版本,也就是 swift --version 的版本,而不是项目中 Build Settings 配置的 Swift 版本,它只影响项目的编译,和 SPM 用哪个 Package.swift 无关。
macOS 的 Swift 工具链限制
macOS 上没法抛开 Xcode 单独装或配置 Swift 工具链。Swift 工具链是跟 Xcode 绑定的。
macOS Tahoe 与 Xcode 版本的限制
- macOS Tahoe 无法使用 Xcode 15
- Xcode 16.14 就已经是 Swift 6.0+ 的版本
- 如果想在 Swift 5.x 的基础上使用 SPM,就必须使用 Xcode 15.0,但这在 macOS Tahoe 上是不可能的
swift-tools-version 的实际作用
项目的 Package.swift 文件顶部的 // swift-tools-version: 5.9 并不能决定用哪个 Package.swift 文件。这个声明只是指定了解析 Package.swift 所需的最低 SPM 工具版本。
那么单独安装 Swift 5.10.1 toolchain 行不行?
既然 Xcode 自带的工具链是 Swift 6.0+,那能不能单独装个 Swift 5.10.1 的 toolchain?
使用 swiftly 安装 toolchain
swiftly 是 Swift 官方的工具链管理工具,可以装不同版本的 Swift toolchain。
试了一下,用 Swift 5.10.1 toolchain 跑 SPM 下载依赖,结果又报错了:
Computing version for https://github.com/devxoul/Then.git
error: Invalid manifest (compiled with: ["/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/bin/swiftc", "-vfsoverlay", "/var/folders/56/hxctgzds50b8p0fw6myh2y500000gn/T/TemporaryDirectory.qkPC2f/vfs.yaml", "-L", "/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/lib/swift/pm/ManifestAPI", "-lPackageDescription", "-Xlinker", "-rpath", "-Xlinker", "/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/lib/swift/pm/ManifestAPI", "-target", "arm64-apple-macosx12.0", "-sdk", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk", "-F", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks", "-I", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib", "-L", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib", "-swift-version", "5", "-I", "/Users/rakuyo/Library/Developer/Toolchains/swift-5.10.1-RELEASE.xctoolchain/usr/lib/swift/pm/ManifestAPI", "-sdk", "/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk", "-package-description-version", "5.0.0", "/Package.swift", "-Xfrontend", "-disable-implicit-concurrency-module-import", "-Xfrontend", "-disable-implicit-string-processing-module-import", "-o", "/var/folders/56/hxctgzds50b8p0fw6myh2y500000gn/T/TemporaryDirectory.EKOhFy/then-manifest"])
error: fatalError
error: compile command failed due to signal 6 (use -v to see invocation)
/Applications/Xcode-26.1.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX26.1.sdk/System/Library/Frameworks/Foundation.framework/Modules/Foundation.swiftmodule/arm64e-apple-macos.swiftinterface:1114:149: error: expected ',' separator
public mutating func transform<E>(updating range: inout Swift.Range<Foundation.AttributedString.Index>, body: (inout Foundation.AttributedString) throws(E) -> Swift.Void) throws(E) where E : Swift.Error
看报错信息就知道了,虽然用了 Swift 5.10.1 的编译器,但 SPM 还是在链接 Xcode 26.1 的 SDK。Swift 5.10.1 的编译器看不懂 macOS 26.1 SDK 里的新语法(比如 throws(E) 这种类型化抛出),所以编译失败。
结论
单独装 Swift 5.10.1 toolchain 也不行:
- SPM 虽然用了旧版本的 Swift 编译器
- 但它还是链接到了 Xcode 26.1 的 SDK
- 旧版本编译器处理不了新版本 SDK 的语法
- Swift toolchain 和 Xcode SDK 之间有版本兼容性问题
官方 Issues 的发现
调查的时候翻了下 swift-collections 的 Issues,找到了相关讨论:#546。
看起来社区里也有人遇到类似问题,但官方并没有在 Package@swift-6.0.swift 里默认加上 .enableExperimentalFeature("Lifetimes") 配置。
解决方案
折腾了一圈,最后的结论就是:
- 只能用
Package@swift-6.0.swift:因为工具链是 Swift 6.0+,SPM 会自动选这个文件 - 必须显式启用 Lifetimes 特性:得在项目配置里加上
-enable-experimental-feature Lifetimes编译参数
具体配置看你用什么构建工具:
使用 Tuist
在 Tuist 的项目配置中,需要为依赖 swift-collections 的 target 添加编译参数:
.target(
name: "YourTarget",
// ... 其他配置
settings: .settings(
base: [
"OTHER_SWIFT_FLAGS": "$(inherited) -enable-experimental-feature Lifetimes"
]
)
)
使用 Xcode 项目
在 Build Settings 中,找到 "Other Swift Flags",添加:
-enable-experimental-feature Lifetimes
意外收获:Tuist 的 Swift 版本默认配置
调查的时候还发现了个 Tuist 相关的事情。
Tuist 默认用的是 SWIFT_VERSION = 5.0。想用 Swift 6.0 的话,也得显式指定。
相关信息:
- PR:#4679
- 代码定义:DefaultSettingsProvider.swift
- 社区讨论:Can't change Swift version for targets (from 5 to 6)
用 Tuist 并且想用 Swift 6.0 的话,得在配置里明确指定:
.target(
name: "YourTarget",
// ... 其他配置
settings: .settings(
base: [
"SWIFT_VERSION": "6.0"
]
)
)
总结
这次折腾下来,对 SPM 的工作机制算是有了更深的理解:
- SPM 用的是工具链版本:SPM 根据
swift --version的版本(也就是工具链版本)来选 Package.swift 文件,不是项目配置的 Swift 版本 - Package@swift-X.X.swift 的优先级:工具链版本满足条件时,SPM 会优先用带版本后缀的 Package.swift 文件
- 实验性特性得显式启用:即使代码里用了实验性特性,Package.swift 里没启用的话,也得在项目级别显式启用
- macOS 的工具链限制:macOS 上 Swift 工具链跟 Xcode 是绑定的,不像 Linux 那样能灵活切换
- Toolchain 与 SDK 的兼容性:单独装了旧版本 Swift toolchain 也没用,它可能跟新版本 Xcode SDK 不兼容