Rob*_*Rob 10 ios swift observableobject ios17 observation-framework
回到 WWDC 2021\xe2\x80\x99s Discover concurrency in SwiftUI,他们建议您将ObservableObject对象与主要参与者隔离。例如:
struct ContentView: View {\n @StateObject var viewModel = ViewModel()\n\n var body: some View {\n Text("\\(viewModel.count)")\n .task {\n try? await viewModel.start()\n }\n }\n}\n\n@MainActor \nclass ViewModel: ObservableObject {\n var count = 0\n\n func start() async throws {\n while count < 10 {\n count += 1\n try await Task.sleep(for: .seconds(1))\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n但在 iOS 17\xe2\x80\x99s观察框架(如 WWDC 2023\xe2\x80\x99s Discover Observation in SwiftUI中介绍)中,似乎不再需要隔离到主要参与者来防止在后台线程。例如,以下内容不会出现有关从后台启动 UI 更新的警告:
\nstruct ContentView: View {\n var viewModel = ViewModel() // was `@StateObject var viewModel = ViewModel()`\n\n var body: some View {\n Text("\\(viewModel.count)")\n .task {\n try? await viewModel.start()\n }\n }\n}\n\n@Observable class ViewModel { // was `@MainActor class ViewModel: ObservableObject {\xe2\x80\xa6}`\n var count = 0 // was `@Published`\n\n func start() async throws {\n while count < 10 {\n count += 1\n try await Task.sleep(for: .seconds(1))\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n目前尚不清楚消除主要参与者隔离的底层机制是什么,但它确实有效。
\n但是,如果您ViewModel出于不从后台更新 UI 以外的原因希望将 Actor 隔离,该怎么办?例如,也许我只是想避免这个@Observable对象中的竞争?SE-0395表示它(尚)不支持可观察actor类型:
\n\n未来增强的另一个重点领域是对可观察
\nactor类型的支持。这需要对参与者当前不存在的关键路径进行特定处理。
但是,如果一个class演员与某个全局演员(例如主要演员)隔离呢?看来我可以将视图模型与主要参与者隔离,但随后我收到错误View:
\n\n在同步非隔离上下文中调用主参与者隔离初始化程序“init()”
\n
View我也可以通过将 与主要演员隔离来解决这个错误。例如,以下内容似乎有效:
@MainActor\nstruct ContentView: View {\n var viewModel = ViewModel()\n\n var body: some View {\n Text("\\(viewModel.count)")\n .task {\n try? await viewModel.start()\n }\n }\n}\n\n@MainActor\n@Observable\nclass ViewModel {\n var count = 0\n\n func start() async throws {\n while count < 10 {\n count += 1\n try await Task.sleep(for: .seconds(1))\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n但将整体View与主要演员隔离开来感觉是错误的,因为苹果显然选择不这样做(出于我无法理解的原因)。那么,简而言之,如何将类型隔离@Observable到全局参与者(例如主要参与者)?
我只有一个解决这个问题的方法,它可能不适用于所有情况。
\n但首先,问题是:
\n给定一个使用并初始化模型的SwiftUI 视图:
\nstruct ContentView: View {\n @State var viewModel = ViewModel()\n\n var body: some View {\n ...\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n以及相应的模型,它使用 @MainActor 来同步其成员:
\n@MainActor\n@Observable\nclass ViewModel {\n var count: Int = 0\n \n func foo() async throws {\n // asynchronously mutates member `count` which \n // needs to be synchronised. Here, through \n // using `@MainActor`. That way, it\'s guaranteed \n // that mutations on `count` happen solely on \n // the main thread.\n ...\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n当尝试编译时,我们在 struct 中收到错误ContentenView:
@State var viewModel = ViewModel() <== Call to main actor-isolated initializer \'init()\' in a synchronous nonisolated context\nRun Code Online (Sandbox Code Playgroud)\n也就是说,编译器要确保 的初始化器Model将在主线程上调用。虽然我们直观地假设,无论如何都会出现这种情况,但它毕竟是一个视图,但编译器需要明确的事实。
这一要求的原因并不那么明显。通常,我们在其他语言中保证了线程安全,其中在通过其他方式访问成员时在任何线程上调用构造函数。
\n对于 Swift,我们可以阅读更多相关信息\n On Actors and Initialization, SE-0327,特别是:\n overly-restrictive-non-async-initializers
\n将 SwiftUI 视图与主要参与者关联是一种解决方案,但今天可能会导致其他问题。
\n另一个解决方案可能只是声明初始化程序非隔离- 但要小心 \xe2\x80\x93 它可能会破坏同步。在这种情况下,可以通过显式声明初始化程序为空主体的非隔离来工作:
\n@MainActor\n@Observable\nclass ViewModel {\n var count: Int = 0\n \n nonisolated init() {}\n \n func start() async throws {\n while count < 10 {\n count += 1\n try await Task.sleep(for: .seconds(1))\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n笔记:
\n为了使用空的非隔离初始化程序,所有成员都必须在声明时进行初始化。例如:
\nclass ViewModel {\n var count: Int = 0\n ...\nRun Code Online (Sandbox Code Playgroud)\n非隔离初始化程序无法初始化/设置成员。如果我们尝试,我们会得到错误:
\n\n\n主要参与者隔离属性“count”无法从非隔离上下文中发生突变
\n
警告
\n声明为非隔离的更复杂的初始化程序可能容易出现数据争用!请仔细阅读以上链接。
\n这是当前问题的解决方法。我希望,这些事情将来能得到更多完成。
\n| 归档时间: |
|
| 查看次数: |
1169 次 |
| 最近记录: |