我是否可以使用SWIFT中的参与者始终调用主线程上的函数? [英] Can I use actors in Swift to always call a function on the main thread?
问题描述
我最近看到SWIFT在Swift 5.5中引入了对Actor模型的并发支持。此模型使安全并发代码能够在我们具有共享、可变状态时避免数据争用。
我希望在我的应用程序的用户界面中避免主线程数据竞争。为此,在我设置UIImageView.image
属性或UIButton
样式的任何地方,我都在调用点处包装DispatchQueue.main.async
。
// 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)
}
}
但这看起来像是很多样板,我不会说我喜欢将其用于具有多层嵌套的复杂函数。
SWIFT对Actors的支持的发布看起来是一个完美的解决方案。那么,有没有办法让我的代码更安全,也就是始终使用Actor调用主线程上的UI函数?
推荐答案
参与者隔离和重入现已在SWIFT标准库中实现。因此,苹果建议将该模型用于具有许多新的并发功能的并发逻辑,以避免数据竞争。我们现在有了一个更干净的替代方案,而不是基于锁的同步(许多样板)。
包括UIViewController
和UILabel
在内的一些UIKit
类现在具有对@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
应用到类型本身。这确保了所有方法和属性都在MainDispatchQueue
上调度。
然后,我们可以使用非UI逻辑的关键字nonisolated
手动选择退出主线程。
@MainActor class ListViewModel: ObservableObject {
func onButtonTap(...) { ... }
nonisolated func fetchLatestAndDisplay() async { ... }
}
在actor
内调用onButtonTap
时,不需要显式指定await
。
解决方案3(适用于块和函数)
我们还可以使用:
在actor
外部调用主线程上的函数
func onButtonTap(...) async {
await MainActor.run {
....
}
}
在不同的actor
中:
func onButtonTap(...) {
await MainActor.run {
....
}
}
如果我们想从MainActor.run
内返回,只需在签名中指定:
func onButtonTap(...) async -> Int {
let result = await MainActor.run { () -> Int in
return 3012
}
return result
}
此解决方案比上面的两个解决方案干净一些,这两个解决方案最适合在MainActor
上包装整个函数。但是,actor.run
还允许在一个func
中的actor
之间使用线程间代码(thx@Bill表示建议)。
解决方案4(在非异步函数中工作的块解决方案)
在解决方案3的@MainActor
上计划块的替代方法:
func onButtonTap(...) {
Task { @MainActor in
....
}
}
与解决方案3相比,这里的优势在于封闭的func
不需要标记为async
。但是,请注意,这会在以后调度块,而不是像解决方案3中那样立即调度。
摘要
参与者使SWIFT代码更安全、更干净、更易于编写。不要过度使用它们,但将UI代码分派到主线程是一个很好的用例。请注意,由于该功能仍处于测试阶段,因此该框架可能会在未来进一步更改/改进。
奖金备注
由于我们可以很容易地将actor
关键字与class
或struct
互换使用,因此我建议将关键字限制在严格需要并发的情况下。使用关键字会增加实例创建的额外开销,因此在没有要管理的共享状态时没有意义。如果您不需要共享状态,则不要不必要地创建它。struct
实例创建非常轻量级,因此大多数情况下最好是创建一个新实例。例如SwiftUI
。
这篇关于我是否可以使用SWIFT中的参与者始终调用主线程上的函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!