3. Swift 版本迁移(Swift 3→4→5 等)
Swift 语言这几年更新确实快。从 3 到 4,再到 5,每次大版本都带来一堆语法变化和 API 调整。
我做过好几次 SDK 的迁移工作,每次都得把整个项目的 Swift 代码扫一遍。
说实话,如果你维护的是 SDK,版本迁移这件事躲不过去——用户会用不同版本的 Swift 编译你的库,你必须保证兼容。
3.1 为什么要关注 Swift 版本迁移?
很多同学觉得只要项目能跑就不升级。但在 SDK 开发里,这是不行的。
举个例子:你用 Swift 3 写的库,用户项目用的是 Swift 4.2,直接就编译不过。
迁移的核心意义有三点:
- 兼容性 —— 让 SDK 能在多个 Swift 版本下正常使用。
- 性能与安全 —— 新版本通常会引入 ABI 稳定、内存优化等改进。
- 社区生态 —— 依赖的第三方库也在升级,你不升就会被卡住。
3.2 Swift 3 → 4:变化量最大的一次
Swift 4 虽然号称源代码兼容 3.2,但实际从 3.0 直接跳过来,改的东西非常多。
我记得那年夏天,光改 String 相关的代码就改了两天。
3.2.1 String 不再是集合
Swift 3 里 String 是 Collection,能直接遍历字符。Swift 4 去掉了这个继承,改成 String 只能通过 .characters 访问。
后来 Swift 4.2 又把 .characters 去掉了,变成直接操作 String。折腾了好几轮。
// Swift 3
let str = "Hello"
for c in str { ... }
// Swift 4
for c in str.characters { ... }
// Swift 4.2+
for c in str { ... }
3.2.2 @objc 推断被缩减
Swift 3 里很多 NSObject 方法会自动暴露给 Objective-C。Swift 4 为了减小二进制体积,收紧了规则。
你要手动加 @objc 或 @objcMembers。
// Swift 3 自动推断
class MyClass: NSObject {
func doSomething() {}
}
// Swift 4 需要显式标记
@objcMembers class MyClass: NSObject {
func doSomething() {}
}
3.2.3 Dictionary 与 Array 的新姿势
Swift 4 引入了 Dictionary 的 mapValues、filter 等方法,数组的 shuffle 也是在那时加的。
另外,以前用 removeAll(keepingCapacity:) 保持容量,Swift 4 之后这个签名微调过,编译会报方法未找到。
3.3 Swift 4 → 5:ABI 终于稳了
Swift 5 最大的成就是 ABI 稳定性。这意味着:你用 Swift 5 编译的二进制,可以在没有 Swift 运行时环境的老系统上跑(iOS 12.2+)。
对于 SDK 开发者来说,这简直是福音。
不过,迁移本身也有一些需要注意的地方。
3.3.1 字符串插值变化
Swift 5 改了字符串插值协议。以前你写 "\(value)" 直接用,现在如果你自定义了 StringInterpolation 协议,必须适配新签名。
// Swift 4
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: MyType) { ... }
}
// Swift 5
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: MyType) { ... }
// 方法名相同,但参数类型和协议结构变了
}
其实大部分普通代码不受影响,只有写了自定义插值逻辑的才会报错。
3.3.2 Result 类型入标准库
Swift 5 把 Result<Success, Failure> 引入了标准库。如果你在项目里自己定义了 Result 枚举,会有命名冲突。
// 自定义的 Result(Swift 4 常用)
enum Result<T> {
case success(T)
case failure(Error)
}
// 迁移到 Swift 5 后,需要改名字或用标准库的
swift_version = '5.0',这样编译时能尽早暴露问题。
3.3.3 标准库 API 清理
Swift 5 废弃了一批旧 API,比如 String.UTF8View 的某些构造器,还有 Collection 上的 cached 方法。
Xcode 的迁移工具能帮你自动替换大部分,但有些边缘情况还是要手动修。
3.4 跨版本迁移的通用策略
如果你要同时支持 Swift 3、4、5,那需要点技巧。我一般用 #if swift(>=4.0) 这样的条件编译。
| 编译条件 | 适用场景 |
|---|---|
#if swift(>=5.0) |
Swift 5 特有 API |
#if swift(>=4.0) && <5.0 |
Swift 4 但非 5 |
#if !swift(>=4.0) |
Swift 3 或更低 |
不过这也容易让代码变得难以维护。我的做法是:尽量让 SDK 只支持最新的两个大版本,用户如果太老就建议升级。
3.5 迁移工作流与工具
- Xcode 迁移工具:Edit → Convert → To Current Swift Syntax。它很聪明,但有时会遗漏自定义桥接。
- 先单元测试:迁移前确保测试覆盖率够高。我吃过亏,迁移完一堆编译错误,测试一跑发现逻辑都变了。
- 分模块迁移:模块化项目可以逐个 target 做迁移,方便回滚。
- 检查公开 API 头文件:如果是混编项目,要确认 .h 文件里引用的 Swift 类型名没有变化。
3.6 迁移中的常见坑
- 可选绑定语法:Swift 4 开始,
if let x = x这种写法偶尔会被编译器认为 ambiguous,需要改成if let x = self.x。 - Selector 字符串:Swift 3 里允许
#selector(methodName),Swift 4 收紧了对方法可见性的检查,必须加 @objc。 - Core Graphics 类型:有些 CGFloat 相关的 API 在 Swift 4 后从函数变成了属性,比如
ceil(x)→x.rounded(.up)。 - NSError 桥接:Swift 3 里 Error 协议有默认行为,Swift 4 开始强制要求类型显式实现 Error 或 LocalizedError。
3.7 迁移决策流程图
下面这张图能帮你快速判断自己该从哪个版本开始迁移,以及每步需要注意的核心变化。
3.8 小结
Swift 版本迁移不可怕,但一定要有章法。
我的经验是:提前做好测试,逐步升级,善用工具。每次迁移后做一次全量回归,比事后修 bug 省时得多。
你想想看,如果 SDK 用户一集成就报几十个编译错误,那体验得多糟糕。