Arj*_*jan 41 async-await swift5 swiftui
更新2:我怀疑这个问题因为我描述的可能的解决方案而被投票。为了清楚起见,突出显示了它。
更新1:这个问题得到了很多人的关注。如果您认为可以通过您自己遇到错误的情况来增强问题,请在评论中简要描述您的情况,以便我们可以使此问答更有价值。如果您对您的问题版本有解决方案,请将其添加为答案。
Task.detached我想在使用和函数执行异步后台工作后更新 UI async。
但是,我在构建过程中收到构建错误错误Reference to captured var 'a' in concurrently-executing code。
我尝试了一些方法,在更新 UI 之前将变量转换为 alet constant是唯一有效的方法。为什么我需要先创建一个 let 常量才能更新 UI?还有其他选择吗?
class ViewModel: ObservableObject {
@Published var something: String?
init() {
Task.detached(priority: .userInitiated) {
await self.doVariousStuff()
}
}
private func doVariousStuff() async {
var a = "a"
let b = await doSomeAsyncStuff()
a.append(b)
something = a /* Not working,
Gives
- runtime warning `Publishing changes from
background threads is not allowed; make sure to
publish values from the main thread (via operators
like receive(on:)) on model updates.`
or, if `something` is @MainActor:
- buildtime error `Property 'something' isolated
to global actor 'MainActor' can not be mutated
from this context`
*/
await MainActor.run {
something = a
} /* Not working,
Gives buildtime error "Reference to captured var 'a'
in concurrently-executing code" error during build
*/
DispatchQueue.main.async {
self.something = a
} /* Not working,
Gives buildtime error "Reference to captured var 'a'
in concurrently-executing code" error during build
*/
/*
This however, works!
*/
let c = a
await MainActor.run {
something = c
}
}
private func doSomeAsyncStuff() async -> String {
return "b"
}
}
Run Code Online (Sandbox Code Playgroud)
dav*_*sdk 27
简而言之,something必须从主线程进行修改,并且只有 Sendable 类型可以从一个 actor 传递到另一个 actor。让我们深入研究细节。
something必须从主线程修改。这是因为必须从主线程修改@Publishedan 中的属性。ObservableObject缺少这方面的文档(如果有人找到官方文档的链接,我将更新此答案)。但由于 an 的订阅者ObservableObject可能是 SwiftUI View,所以这是有道理的。Apple 可以决定View在主线程上订阅和接收事件,但这会隐藏这样一个事实:从多个线程发送 UI 更新事件是危险的。
只有可发送类型可以从一个参与者传递到另一个参与者。有两种方法可以解决这个问题。首先我们可以使aSendable。其次,我们可以确保不跨越aActor 边界,并使所有代码在同一个 Actor 上运行(在这种情况下,它必须是 Main Actor,因为它保证在主线程上运行)。
让我们看看如何使其a可发送并研究以下案例:
await MainActor.run {
something = a
}
Run Code Online (Sandbox Code Playgroud)
函数中的代码doVariousStuff()可以从任何参与者运行;我们称其为 Actor A。a属于 Actor A,并且必须将其发送给 Main Actor。由于a不符合 Sendable,编译器看不到任何在 Main Actor 上读取a时不会更改的保证。a这在 Swift 并发模型中是不允许的。为了给编译器这样的保证,a必须是可发送的。一种方法是使其保持不变。这就是为什么这有效:
let c = a
await MainActor.run {
something = c
}
Run Code Online (Sandbox Code Playgroud)
即使可以改进为:
await MainActor.run { [a] in
something = a
}
Run Code Online (Sandbox Code Playgroud)
捕获a为常数。还有其他 Sendable 类型,详细信息可以在这里找到https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID649。
解决这个问题的另一种方法是让所有代码在同一个参与者上运行。最简单的方法是按照 Asperi 的建议ViewModel进行标记。@MainActor这将保证doVariousStuff()从 Main Actor 运行,因此它可以设置something. 作为旁注,athen 属于主要演员,所以(即使它毫无意义)await MainActor.run { something = a }可以工作。
请注意,参与者不是线程。Actor A 可以从任何线程运行。它可以在一个线程上启动,然后在任何一个线程之后继续在另一个线程上await。它甚至可以部分在主线程上运行。重要的是,一个参与者一次只能从一个线程运行。任何 Actor 都可以从任何线程运行这一规则的唯一例外是仅在主线程上运行的 Main Actor。
Asp*_*eri 12
让你的可观察对象成为主角,比如
@MainActor // << here !!
class ViewModel: ObservableObject {
@Published var something: String?
init() {
Task.detached(priority: .userInitiated) {
await self.doVariousStuff()
}
}
private func doVariousStuff() async {
var a = "a"
let b = await doSomeAsyncStuff()
a.append(b)
something = a // << now this works !!
}
private func doSomeAsyncStuff() async -> String {
return "b"
}
}
Run Code Online (Sandbox Code Playgroud)
使用 Xcode 13 / iOS 15 进行测试
| 归档时间: |
|
| 查看次数: |
36512 次 |
| 最近记录: |