• Swift官方竟然悄悄地发布了Swift 6.2测试版
  • 发布于 2天前
  • 38 热度
    0 评论
前言
前几天网上闲逛时意外发现 Swift 官方悄咪咪地发布了 Swift 6.2 测试版。朋友圈翻了一圈,发现好多开发者竟然都没注意到这个更新!但深挖下来,这个版本藏着不少让人惊喜的改进,特别是对我们这些每天跟编译时间和类型推断较劲的人来说。说真的,Swift 6.2 带来的这波更新实在让我兴奋到睡不着觉。原始标识符、函数回溯 API、Task 命名...但最让我激动的是几个数组操作的改进,尤其是 InlineArray 和那些细节优化。好了,废话不多说,来看看我熬夜总结的这些能让你代码质量和开发效率直接起飞的更新吧!

Swift 6.2 的重大新特性
Swift 6.2 这次真的带了一卡车更新,看起来就像是 Swift 团队把原本要放在 Swift 6.0 里的功能都塞进来了。简单罗列一下主要更新:
1.控制默认 Actor 隔离推断(SE-0466)
2.原始标识符(SE-0451)
3.字符串插值默认值(SE-0477)
4.enumerated() 的 Collection 一致性(SE-0459)
5.任务命名和回溯 API

6.Swift Testing 框架的增强


我个人最关心的是几个实用的改进,这些功能特别实用,直接影响日常开发效率。下面就重点聊聊这几个部分。

控制默认 Actor 隔离推断
SE-0466 带来了超棒的特性:让代码默认在单个 actor 上运行的能力!简单说,就是可以让我们的程序默认回到单线程模式,大部分代码都在主 actor 上运行,除非你特别指定。这意味着什么?对很多 app 开发者来说,你基本不用考虑并发问题了,直到你真正需要时才去处理。只需添加 -default-isolation MainActor 到编译选项,下面的代码就能正常工作:
@MainActor
class DataController {
    func load() { }
    func save() { }
}

struct App {
    let controller = DataController()

    init() {
        controller.load()
    }
}
看到没,App 结构体创建并使用了一个 main actor 隔离的类型,而自己并没有标记 @MainActor,但这完全没问题!因为这个特性会自动应用——我们甚至可以移除那个孤零零的 @MainActor 注解,它仍然有效。

别担心这会导致并发问题,有几点很重要:
1.这个配置是按模块应用的,所以引入的外部模块仍然可以在其他 actor 上运行
2.网络请求等代码仍然能正常工作——URLSession.shared.data(from:) 会在自己的任务中运行,不会阻塞你的代码
3.现代 iPhone 的单个 CPU 核心速度超过 4GHz,大量 iOS 应用完全可以串行完成工作
4.很多开发者已经在用"全都标记 @MainActor"的默认做法,只在需要时才改变

5.这个改变是 Swift 团队改善数据竞争安全可接近性的整体愿景的一部分


虽然这看起来和 Swift 5.5 以来的并发工作方向相反,但它解决了一个越来越大的问题:Swift 并发不容易学,而且很多应用根本不需要它。真正的问题是苹果会不会在 Xcode 中为新项目默认启用这个功能。我真心希望如此,这样 Xcode 就可以默认使用 Swift 6 语言版本,而不会引入大多数开发者不需要考虑的错误和警告。

原始标识符(Raw identifiers)
SE-0451 大幅扩展了我们可以用于创建标识符的字符范围——变量名、函数名、枚举 case 等——所以现在只要放在反引号里,基本上可以随心所欲命名了。
Swift 6.2 及以后,这种代码是合法的:
func `function name with spaces`() {
    print("Hello, world!")
}

`function name with spaces`()
觉得这种命名没啥用?考虑这个例子:
enum HTTPError: String {
    case `401` = "Unauthorized"
    case `404` = "Not Found"
    case `500` = "Internal Server Error"
    case `502` = "Bad Gateway"
}
这样每个 HTTP 错误码都成了枚举中的一个 case,之前只能写成 case _401 或 case error401 这样的形式。使用数字时,要么每次都限定类型以避免 Swift 混淆,要么小心放置反引号:
let error = HTTPError.401
switch error {
case HTTPError.401, HTTPError.404:
    print("Client error: \(error.rawValue)")
default:
    print("Server error: \(error.rawValue)")
}
另一种方法是只把数字部分(不包括前面的点)放在反引号中:
switch error {
case .`401`, .`404`:
    print("Client error: \(error.rawValue)")
default:
    print("Server error: \(error.rawValue)")
}
这个改进对 Swift Testing 最有利,测试名称现在可以直接用人类可读的形式,不必用驼峰命名然后添加额外的描述字符串。比如,不必写成:
import Testing
@Test("Strip HTML tags from string")
func stripHTMLTagsFromString() {
    // 测试代码
}
可以直接写成:
@Test
func `Strip HTML tags from string`() {
    // 测试代码
}
简单说,少写了重复代码,太棒了!有个小细节要注意:"原始标识符可以以操作符字符开始、包含或结束,但不能仅由操作符字符组成。" 所以,可以在标识符中放入 + 和 - 这样的操作符,但不能只有操作符。

字符串插值中的默认值
SE-0477 对处理可选值的字符串插值做了一个小但精妙的改进,允许我们提供一个额外的 default 值,在可选值为 nil 时使用。
最简单的形式是这样:
var name: String? = nil
print("Hello, \(name, default: "Anonymous")!")
代替了这种写法:
print("Hello, \(name ?? "Anonymous")!")
乍看可能不是很大的改进,但关键在于 nil 合并操作符不适用于不同类型。所以,这样的代码现在被允许:
var score: Int? = nil
print("You scored \(score, default: "nothing") in the test.")
以前我们得这么写:
print("You scored \(score.map(String.init) ?? "nothing") in the test.")
或者:
print("You scored \(score != nil ? "\(score!)" : "nothing") in the test.")
Swift 编译器现在会自动完成类型转换,所以即使 score 是 Int? 类型,default 值也可以是一个字符串。这让代码更干净、更易读。

enumerated() 终于变聪明了
SE-0459 让 enumerated() 返回的类型现在遵循 Collection 协议,这个小改动解决了我们好多痛点。最直接的好处是,现在能轻松在 SwiftUI 的 List 或 ForEach 中用 enumerated():
import SwiftUI

struct ContentView: View {
    var names = ["Bernard", "Laverne", "Hoagie"]

    var body: some View {
        List(names.enumerated(), id: \.offset) { values in
            Text("用户 \(values.offset + 1): \(values.element)")
        }
    }
}
以前写 SwiftUI 时每次想用 enumerated() 都要额外包装一层,现在终于不用了!提案还带来了不少性能优势,比如 (1000..<2000).enumerated().dropFirst(500) 变成常数时间操作。之前这种操作慢得要死,现在快多了。

任务命名和回溯
Swift 6.2 引入了任务命名和回溯 API,这对调试并发代码非常有用。当你启动一个新任务时,可以给它一个有意义的名称:
let task = Task(name: "Fetch user details") {
    let url = URL(string: "https://example.com/users/1")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}
然后,当你需要调试问题时,这些任务名称会出现在调试器中,让你更容易理解应用程序在做什么。Swift 也支持回溯,允许我们在运行时捕获并检查程序状态:
let backtrace = Task.currentBacktrace()
for entry in backtrace {
    print(entry)
}
这让我们可以看到任务是如何创建的,以及它们是如何相互关联的,大大简化了并发代码的调试。

Swift Testing 增强
Swift 6.2 为 Swift Testing 框架带来了三项主要改进:
1.进程退出测试:可以测试导致应用崩溃的代码,比如 precondition 失败或 fatalError()
2.附件:可以将调试日志或生成的数据文件直接附加到测试中,在测试失败时提供更多上下文
3.评估条件特性的公共 API:可以在测试函数之外评估相同的条件
例如,进程退出测试可以这样使用:
@Test func invalidDiceRollsFail() async throws {
    let dice = Dice()
    // 堆代码 duidaima.com
    await #expect(processExitsWith: .failure) {
        let _ = dice.roll(sides: 0)
    }
}
这打开了一系列以前很难甚至不可能测试的场景。

结语:这次更新真的香
Swift 6.2 这波更新带来了很多实用改进,特别是在并发和类型安全方面。这些改进能帮我们写出更高效、更易维护的代码。这次更新感觉就是 Swift 团队交付了许多人想象中 Swift 6.0 应该有的东西——并发支持越来越完善,同时有一系列实用选择,帮助平滑语言的学习曲线。并不是所有项目都需要这些改进,但我敢肯定至少有一两个会成为你的标准实践。

最后要提醒一下,虽然 Swift 6.2 这么多激动人心的新功能,但正式版发布前可能会有变化,用在生产环境前还是要谨慎测试。你觉得这些新特性中,哪个对你的开发工作影响最大?来评论区分享一下呗!
用户评论