闽公网安备 35020302035485号
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)
}
}
这打开了一系列以前很难甚至不可能测试的场景。