在WWDC 2021的Discover concurrency in SwiftUI中,他们建议将
但在iOS 17的Observation框架中(正如WWDC 2023的在SwiftUI中发现观察所介绍的),似乎不再需要将主要操作员隔离以防止UI更新在后台线程上触发。例如,以下代码可以正常工作,而不会出现关于从后台启动UI更新的警告:
但是,将整个
ObservableObject
对象隔离到主要的actor中。例如:struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
Text("\(viewModel.count)")
.task {
try? await viewModel.start()
}
}
}
@MainActor
class ViewModel: ObservableObject {
var count = 0
func start() async throws {
while count < 10 {
count += 1
try await Task.sleep(for: .seconds(1))
}
}
}
但在iOS 17的Observation框架中(正如WWDC 2023的在SwiftUI中发现观察所介绍的),似乎不再需要将主要操作员隔离以防止UI更新在后台线程上触发。例如,以下代码可以正常工作,而不会出现关于从后台启动UI更新的警告:
struct ContentView: View {
var viewModel = ViewModel() // was `@StateObject var viewModel = ViewModel()`
var body: some View {
Text("\(viewModel.count)")
.task {
try? await viewModel.start()
}
}
}
@Observable class ViewModel { // was `@MainActor class ViewModel: ObservableObject {…}`
var count = 0 // was `@Published`
func start() async throws {
while count < 10 {
count += 1
try await Task.sleep(for: .seconds(1))
}
}
}
对于避免主要角色隔离的根本机制并不立即明显,但它有效。
但是,如果你希望除了不从后台更新 UI 之外的其他原因,ViewModel
也被隔离为角色,该怎么办呢? 例如,我只想在这个 @Observable
对象中避免竞争?SE-0395 表明它(当前)不支持可观察的 actor
类型:
未来增强的另一个重点领域是支持可观察的
actor
类型。这将需要针对目前角色尚不存在的关键路径进行特定处理。
那么,对于某些全局角色(如主要角色)隔离的 class
呢?看起来我可以将视图模型隔离到主要角色,但是在 View
中会出现错误:
在同步非隔离上下文中调用主要角色隔离的初始化程序 'init()'
我可以通过将View
隔离到主要的角色中来规避该错误。例如,以下似乎有效:
@MainActor
struct ContentView: View {
var viewModel = ViewModel()
var body: some View {
Text("\(viewModel.count)")
.task {
try? await viewModel.start()
}
}
}
@MainActor
@Observable
class ViewModel {
var count = 0
func start() async throws {
while count < 10 {
count += 1
try await Task.sleep(for: .seconds(1))
}
}
}
但是,将整个
View
隔离到主要角色似乎不太合适,尤其是当苹果明显选择不这样做时(原因我无法理解)。所以,简而言之,如何将@Observable
类型隔离到全局角色(比如主要角色)?
@Observable
宏单独并不会以任何方式显式地使类的成员进行同步或隔离。实际上,突变是同步的,并且发生在与当前调度队列执行器相关联的任何线程上。因此,start()
只有在任务使用相同的底层线程或同步调度队列时才可能是线程安全的。然而,SwiftUI视图只能从主线程读取值(与设置器异步)。我怀疑访问模型属性的视图是否是线程安全的。 - undefined@Observable
可能会防止主线程UI更新警告,但它不是线程安全的。(顺便说一下,start
的非隔离async
版本在“通用执行器”上运行,利用了协作线程池。)恢复线程安全性是我希望将class
隔离到全局actor的整个原因。就像你在下面所说的那样,使用@MainActor
类型和nonisolated
init
是另一种解决这个问题的方法。 - undefinedView
隔离到@MainActor
不再影响#Preview
,因此我已经删除了我问题中的那部分内容。 - undefined