ios - 我可以在 Swift 中使用演员来始终调用主线程上的函数吗?
问题描述
我最近看到 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: String) {
DispatchQueue.main.async {
myImageView.image = UIImage(named: thumbnailName)
}
}
但这看起来像很多样板文件,我不会说我喜欢将它用于具有不止一层嵌套的复杂函数。
对 Actors 的 Swift 支持的发布看起来是一个完美的解决方案。那么,有没有办法让我的代码更安全,即始终使用 Actors 在主线程上调用 UI 函数?
解决方案
Actor 隔离和重入现在在 Swift 标准库中实现。因此,Apple 建议使用具有许多新并发功能的并发逻辑模型来避免数据竞争。我们现在有了一个更简洁的替代方案,而不是基于锁的同步(大量样板文件)。
一些UIKit
类,包括UIViewController
和UILabel
,现在对@MainActor
. 所以我们只需要在自定义UI相关类中使用注解即可。例如,在上面的代码中,myImageView.image
会自动在主队列上调度。但是,UIImage.init(named:)
调用不会在视图控制器之外的主线程上自动分派。
在一般情况下,@MainActor
对于并发访问 UI 相关状态很有用,并且是最容易做到的,即使我们也可以手动调度。我在下面概述了潜在的解决方案:
解决方案 1
最简单的可能。此属性在与 UI 相关的类中可能很有用。@MainActor
Apple 使用方法注释使该过程更加简洁:
@MainActor func setImage(thumbnailName: String) {
myImageView.image = UIImage(image: thumbnailName)
}
这段代码等效于 wrap in 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
当我们onButtonTap
在actor
.
解决方案 3(适用于块以及函数)
我们还可以在一个外部的主线程上调用函数actor
:
func onButtonTap(...) async {
await MainActor.run {
....
}
}
在不同的内部actor
:
func onButtonTap(...) {
await MainActor.run {
....
}
}
如果我们想从 a 中返回MainActor.run
,只需在签名中指定:
func onButtonTap(...) async -> Int {
let result = await MainActor.run { () -> Int in
return 3012
}
return result
}
此解决方案比上述两个最适合将整个函数包装在MainActor
. 但是,actor.run
也允许actor
s 之间的线程间代码合而为一func
(感谢@Bill 的建议)。
解决方案 4(适用于非异步函数的块解决方案)
@MainActor
在解决方案 3上安排块的另一种方法:
func onButtonTap(...) {
Task { @MainActor in
....
}
}
与解决方案 3 相比,这里的优点是func
不需要将封闭标记为async
. 但是请注意,这会稍后而不是像解决方案 3 中那样立即分派块。
概括
Actor 使 Swift 代码更安全、更简洁、更易于编写。不要过度使用它们,但是将 UI 代码分派到主线程是一个很好的用例。请注意,由于该功能仍处于测试阶段,因此该框架将来可能会进一步更改/改进。
奖金票据
由于我们可以轻松地将关键字与oractor
互换使用,因此我建议仅将关键字限制在严格需要并发性的情况下。使用关键字会增加实例创建的额外开销,因此在没有共享状态需要管理时没有任何意义。class
struct
如果您不需要共享状态,则不要不必要地创建它。struct
实例创建是如此轻量级,以至于大多数时候创建一个新实例会更好。例如SwiftUI
。
推荐阅读
- unity3d - 如何为除一个对象(专注于该对象)之外的所有场景创建模糊效果?
- google-apps-script - 使用 Google Appscript 中的触发器设置函数以调用多个函数
- sharepoint - SP-Starter-Kit 中的框架图块不起作用
- android - 重新初始化 Dagger 2 提供的对象
- macos - 在 OS X 上安装 ROS2 - 错误:没有名为“rclpy._rclpy”的模块
- postgresql - 设置postgres环境变量运行镜像
- django-rest-framework - 如何覆盖 Django Rest Framework 中的 partial_update?
- c# - 从 azure 函数 C# 将文件保存到数据湖中
- javascript - 如何创建具有特定数据的嵌套数组?
- php - WooCommerce 产品类别和产品标签基础产品永久链接不起作用