在并发执行的代码中使用对捕获变量的引用

Use reference to captured variable in concurrently-executing code

更新:这个问题得到了很多意见。如果您认为问题可以通过自己遇到错误的情况得到加强,请在评论中简要描述您的情况,以便我们使本次问答更有价值。如果您对问题的版本有解决方案,请将其添加为答案。

更新 2:我怀疑这个问题因为我描述的可能解决方案而被投票。为了清楚起见,将其突出显示。


我想在使用 Task.detachedasync 函数完成异步后台工作后更新 UI。

但是,我在构建过程中遇到构建错误 Reference to captured var 'a' in concurrently-executing code 错误。

我尝试了一些方法,在更新 UI 之前将变量转换为 let constant 是唯一可行的方法。为什么我需要在能够更新 UI 之前创建一个 let 常量?有其他选择吗?

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"
    }
} 

让你的可观察对象成为主要演员,比如

@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"
    }
}

测试 Xcode 13 / iOS 15

backup

您可以使用 @State.task 如下:

struct ContentView: View {
    @State var result = ""

    var body: some View {
        HStack {
            Text(result)
        }
        .task {
            result = await Something.doSomeAsyncStuff()  
        }
    }
}

任务在视图出现时开始,在视图消失时取消。此外,如果您使用 .task(id:),它将在 id 值更改时重新启动(也取消之前的任务)。

异步函数可以放在几个不同的地方,通常是某个地方,这样它就可以独立测试。

struct Something {
    static func doSomeAsyncStuff() async -> String {
        return "b"
    }
}