Swift 5.9 新特性: Clock 扩展 sleep(for:)

前言

Clock 协议是在 Swift 5.7 引入的,它可以先将程序休眠等到未来的某个时刻再继续执行。但是它没有 API 让开发者控制程序休眠的时间。而 Task 的静态函数 sleep 不同的是,其同时提供了可以休眠到未来的某个时刻或者具体时间两个 API。

API 中的这种不平衡可能是为所有时钟添加 sleep(for:) 方法的充分理由,但真正的问题发生在处理存在 Clock时。因为 Instant 关联类型被完全擦除,只有Duration 通过主关联类型被保留,所以任何处理 Instant 的 API 都无法被存在时钟访问。这意味着不能在存在时钟上调用 sleep(until:),所以,对于存在时钟来说,你做不了任何事情。

下面通过具体的代码来说明一下。

代码说明

假设你有一个需求:等待 5 s 之后,显示欢迎的字符串。代码可能会这样写:

swift 复制代码
class FeatureModel: ObservableObject {
  @Published var message: String?
  func onAppear() async {
    do {
      try await Task.sleep(until: .now.advanced(by: .seconds(5)))
      self.message = "Welcome!"
    } catch {}
  }
}

假设你想测试上面的代码,那么在测试代码中,你必须就得等待 5s 来跑通测试:

scss 复制代码
let model = FeatureModel()

XCTAssertEqual(model.message, nil)
await model.onAppear() // Waits for 5 seconds
XCTAssertEqual(model.message, "Welcome!")

可能有人会说,这只影响测试代码,但我不写测试代码啊🐶。不光测试代码,如果你使用了 Xcode 的预览,那你的预览页面也会等待 5s 之后才会看见效果。这就意味着你不能快速进行 UI 方面的调整。

尝试解决方案1:使用 Clock 的sleep(until:)

既然 Task 的 sleep 行不通,那可不可以使用 Clock 来进行实现呢:

swift 复制代码
class FeatureModel: ObservableObject {
  @Published var message: String?
  let clock: any Clock<Duration>

  func onAppear() async {
    do {
      try await self.clock.sleep(until: self.clock.now.advanced(by: .seconds(5))) // 编译报错
      self.message = "Welcome!"
    } catch {}
  }
}

当在 Xcode 中写上面的代码时,会发现编译报错。那是因为 Instant 已经被类型擦除了,编译器是没办法访问 .now 然后继续执行的。

类似的下面的代码也是编译不通过的:

lua 复制代码
try await Task.sleep(until: self.clock.now.advanced(by: .seconds(5)), clock: self.clock)

尝试解决方案2:使用泛型

我们通过将 clock 的类型泛型,来进行曲线救国:

swift 复制代码
class FeatureModel<C: Clock<Duration>>: ObservableObject {
  @Published var message: String?
  let clock: C

  func onAppear() async {
    do {
      try await self.clock.sleep(until: self.clock.now.advanced(by: .seconds(5)))
      self.message = "Welcome!"
    } catch {}
  }
}

但这也是有问题的,如果你想代码是可测试可控的话,它会迫使任何使用 FeatureModel 的代码都引入一个泛型。而且 clock 只是其内部的一个属性,但使用该类会依赖 clock 的类型,这会非常奇怪。

最终解决方案:sleep(for:)

最好的解决办法就是使用 Swift5.9 给 Clock 新扩展的 API sleep(for:):

swift 复制代码
class FeatureModel: ObservableObject {
  @Published var message: String?
  let clock: any Clock<Duration>

  func onAppear() async {
    do {
      try await self.clock.sleep(for: .seconds(5)) // 使用新增的 API
      self.message = "Welcome!"
    } catch {}
  }
}

因为它支持了 any Clock<Duration>,所以我们现在可以在生产中使用 ContinuousClock 之类的东西,但在测试中使用自己定义的 TestClock,这样,就可以忽略所有 sleep() 命令以保持测试快速运行。

相关推荐
茶底世界之下4 天前
为什么预览、录制、离线导出不能共享同一个背压策略?
ios·swift
2501_915909064 天前
移动端开发工具链怎么组合比较好,iOS、Android、跨端三套配置方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
东坡肘子4 天前
一场关于 SwiftUI 动画的“原生”之争 -- 肘子的 Swift 周报 #154
人工智能·swiftui·swift
2501_915918415 天前
iOS编程用什么软件好?主流工具Xcode、AppCode、CodeRunner与新兴快蝎对比
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
9765033355 天前
iOS 上架 4.3a 被拒【uniapp专讲】
flutter·ios·objective-c·uniapp·swift
传奇开心果编程6 天前
【SwiftUI入门练中学】第1课 从零开始
学习·ui·ios·swiftui·swift
丿Wayne6 天前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
大熊猫侯佩7 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift
SoaringHeart8 天前
Swift 进阶&封装:仿 Get 给SwiftUI 添加路由导航能力
swiftui·swift
2501_915918418 天前
使用 VS Code 写 Swift 做 iOS 开发,插件能实现哪些功能,还有哪些短板
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程