在WWDC 2021的Discover concurrency in SwiftUI中,他们建议您将ObservableObject
对象隔离到主要参与者。例如:
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的Discover Observation in 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))
}
}
}
型
消除主要参与者隔离的基本机制并不明显,但它确实有效。
但是,如果您希望ViewModel
是隔离的参与者,而不是从后台更新UI,该怎么办呢?例如,也许我只是想避免这个@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
类型隔离到全局参与者(例如主参与者)?
1条答案
按热度按时间deikduxw1#
我只有一个解决这个问题的方法,它可能不适用于所有情况。
但首先,问题是:
给定一个SwiftUI视图,它使用并 * 初始化 * 一个Model:
字符串
以及相应的Model,它使用@MainActor来同步其成员:
型
当尝试编译时,我们在struct
ContentenView
中得到一个错误:型
也就是说,编译器希望确保
Model
的初始化器将在主线程上被调用。虽然我们直觉地认为,无论如何都会是这种情况,但它毕竟是一个视图,编译器需要明确的事实。这一要求的原因并不明显。通常,我们在其他语言中保证了线程安全,其中 * 构造函数 * 在任何线程上被调用,当访问成员时通过其他方式变得安全。
对于Swift,我们可以阅读更多关于On Actors and Initialization,SE-0327的内容,具体如下:过度限制非异步初始化器
将SwiftUI视图关联到主参与者上是一种解决方案,但现在可能会导致其他问题。
另一种解决方案可能只是声明初始化器 nonisolated -但要小心-它可能会破坏同步。在这种情况下,它可以通过显式声明初始化器为非隔离的空主体来工作:
型
注意事项:
为了使用空的非隔离初始化器,所有成员在声明时都必须初始化。举例来说:
型
非隔离初始化器不能初始化/设置成员。如果我们尝试,我们会得到错误:
主参与者隔离属性'count'不能从非隔离上下文中发生变化
注意事项
声明为非隔离的更复杂的初始化器可能容易发生数据竞争!请仔细阅读以上链接。
这是解决当前问题的一种方法。我希望,这些事情在未来得到更多的完成。