我可以在Swift中使用actor始终在主线程上调用函数吗?

我最近看到 Swift 在Swift 5.5 中引入了对 Actor 模型的并发支持。当我们有一个共享的、可变的状态时,这个模型支持安全的并发代码以避免数据竞争。

我想避免应用程序 UI 中的主线程数据竞争。为此,我将DispatchQueue.main.async在调用站点中设置UIImageView.image属性或UIButton样式的任何地方进行包装。

// Original function
func setImage(thumbnailName: String) {
    myImageView.image = UIImage(named: thumbnailName)
}

// Call site
DispatchQueue.main.async {
    myVC.setImage(thumbnailName: "thumbnail")
}

这似乎不安全,因为我必须记住在主队列上手动调度该方法。另一种解决方案如下所示:

func setImage(thumbnailName: UIImage) {
   DispatchQueue.main.async {
      myImageView.image = UIImage(named: thumbnailName)
   }
}

但这看起来像很多样板文件,我不会说我喜欢将它用于具有不止一层嵌套的复杂功能。

Swift 对 Actors 的支持看起来像是一个完美的解决方案。那么,有没有办法让我的代码更安全,即始终使用 Actors 在主线程上调用 UI 函数?

回答

Actor 隔离和重入现在在 Swift 标准库中实现。因此,Apple 建议将该模型用于具有许多新并发特性的并发逻辑,以避免数据竞争。我们现在有了一个更简洁的替代方案,而不是基于锁的同步(大量样板文件)。

UIKit包括UIViewController和在内的一些类UILabel现在对@MainActor. 所以我们只需要在自定义 UI 相关的类中使用注解即可。例如,在上面的代码中,myImageView.image会自动在主队列上分派。但是,UIImage.init(named:)调用不会在视图控制器外部的主线程上自动分派。

在一般情况下,@MainActor对于并发访问 UI 相关状态很有用。我在下面概述了几个解决方案:

解决方案1

最简单的可能。此属性在 UI 相关类中很有用。Apple 使用@MainActor方法注释使该过程更加清晰:

@MainActor func setImage(thumbnailName: String) {
    myImageView.image = UIImage(image: thumbnailName)
}

这段代码相当于包装在 中DispatchQueue.main.async,但调用站点现在是:

await setImage(thumbnailName: "thumbnail")

解决方案2

如果你有自定义 UI 相关的类,我们可以考虑应用@MainActor到类型本身。这确保所有方法和属性都在 main 上分派DispatchQueue

然后我们可以使用nonisolated非 UI 逻辑的关键字手动选择退出主线程。

@MainActor class ListViewModel: ObservableObject {
    func onButtonTap(...) { ... }

    nonisolated func fetchLatestAndDisplay() async { ... }
}

我们并不需要指定await,当我们要求明确onButtonTapactor

概括

Actor 使 Swift 代码更安全、更简洁、更易于编写。不要过度使用它们,但是将 UI 代码分派到主线程是一个很好的用例。请注意,由于该功能仍处于测试阶段,因此该框架将来可能会进一步更改/改进。

奖金笔记

由于我们可以轻松地将actor关键字与classor互换使用struct,因此我建议将关键字限制在严格需要并发的情况下。使用关键字会增加实例创建的额外开销,因此在没有要管理的共享状态时没有意义。

如果您不需要共享状态,则不要不必要地创建它。struct实例创建非常轻量级,大多数时候最好创建一个新实例。例如SwiftUI


以上是我可以在Swift中使用actor始终在主线程上调用函数吗?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>