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
。