6.Swift Testing 框架的增强
@MainActor class DataController { func load() { } func save() { } } struct App { let controller = DataController() init() { controller.load() } }看到没,App 结构体创建并使用了一个 main actor 隔离的类型,而自己并没有标记 @MainActor,但这完全没问题!因为这个特性会自动应用——我们甚至可以移除那个孤零零的 @MainActor 注解,它仍然有效。
5.这个改变是 Swift 团队改善数据竞争安全可接近性的整体愿景的一部分
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`() { // 测试代码 }简单说,少写了重复代码,太棒了!有个小细节要注意:"原始标识符可以以操作符字符开始、包含或结束,但不能仅由操作符字符组成。" 所以,可以在标识符中放入 + 和 - 这样的操作符,但不能只有操作符。
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 值也可以是一个字符串。这让代码更干净、更易读。
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) 变成常数时间操作。之前这种操作慢得要死,现在快多了。
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) }这让我们可以看到任务是如何创建的,以及它们是如何相互关联的,大大简化了并发代码的调试。
@Test func invalidDiceRollsFail() async throws { let dice = Dice() // 堆代码 duidaima.com await #expect(processExitsWith: .failure) { let _ = dice.roll(sides: 0) } }这打开了一系列以前很难甚至不可能测试的场景。