gpt4 book ai didi

SwiftUI 安全更新任务的状态变量

转载 作者:行者123 更新时间:2023-12-03 07:58:38 25 4
gpt4 key购买 nike

这是我的观点:

import SwiftUI

struct ContentView: View {
private let weatherLoader = WeatherLoader()

@State private var temperature = ""
@State private var pressure = ""
@State private var humidity = ""
@State private var tickmark = ""
@State private var refreshable = true

var body: some View {
GeometryReader { metrics in
VStack(spacing: 0) {
Grid(horizontalSpacing: 0, verticalSpacing: 0) {
GridRow {
Text("Температура")
.frame(width: metrics.size.width/2)
Text("\(temperature) °C")
.frame(width: metrics.size.width/2)
}.frame(height: metrics.size.height*0.8*0.25)

GridRow {
Text("Давление")
.frame(width: metrics.size.width/2)
Text("\(pressure) мм рт ст")
.frame(width: metrics.size.width/2)
}.frame(height: metrics.size.height*0.8*0.25)

GridRow {
Text("Влажность")
.frame(width: metrics.size.width/2)
Text("\(humidity) %")
.frame(width: metrics.size.width/2)
}.frame(height: metrics.size.height*0.8*0.25)

GridRow {
Text("Дата обновления")
.frame(width: metrics.size.width/2)
Text("\(tickmark)")
.frame(width: metrics.size.width/2)
}.frame(height: metrics.size.height*0.8*0.25)
}.frame(height: metrics.size.height*0.8)

Button("Обновить") {
refreshable = false
print("handler : \(Thread.current)")
Task.detached {
print("task : \(Thread.current)")
let result = await weatherLoader.loadWeather()
await MainActor.run {
print("main actor: \(Thread.current)")
switch result {
case .success(let item):
temperature = item.temperature
pressure = item.pressure
humidity = item.humidity
tickmark = item.date
case .failure:
temperature = ""
pressure = ""
humidity = ""
tickmark = ""
}

refreshable = true
}
}
}
.disabled(!refreshable)
.padding()
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.frame(width: 320, height: 240)
}
}

问题是 - 从异步上下文更新 @State 变量的正确方法是什么?我发现如果我摆脱 MainActor.run 就不会失败,但是在处理 UIKit 时,我们必须从主线程调用此更新。这里有什么不同吗?我还了解到 Task 继承了 MainActor 上下文,因此我放置了 Task.detached 以确保它是除 main 之外的另一个线程。谁能帮我解释一下吗?

最佳答案

如果您使用运行任务

Task { @MainActor in
//
}

然后任务本身内的代码将在主队列上运行,但它发出的任何异步调用都可以在任何队列上运行。

添加 WeatherLoader 的实现,如下所示:

class WeatherLoader {

func loadWeather() async throws -> Item {
print("load : \(Thread.current)")
try await Task.sleep(nanoseconds: 1_000_000_000)
return Item()
}
}

然后调用如下:

print("handler : \(Thread.current)")
Task { @MainActor in
print("task : \(Thread.current)")
do {
let item = try await weatherLoader.loadWeather()
print("result : \(Thread.current)")
temperature = item.temperature
pressure = item.pressure
humidity = item.humidity
tickmark = item.date
} catch {
temperature = ""
pressure = ""
humidity = ""
tickmark = ""
}

refreshable = true
}

你会看到类似的东西

handler : <_NSMainThread: 0x6000000f02c0>{number = 1, name = main}
task : <_NSMainThread: 0x6000000f02c0>{number = 1, name = main}
load : <NSThread: 0x6000000a1e00>{number = 6, name = (null)}
result : <_NSMainThread: 0x6000000f02c0>{number = 1, name = main}

如您所见,handlertask在主队列上运行,load在其他队列上运行,然后result 又回到了主队列。由于任务本身内的代码保证在主队列上运行,因此可以安全地从那里更新状态变量。

正如您上面提到的,在这种情况下实际上不需要 @MainActor inTask(priority:operation:) 继承调用者的优先级和 Actor 上下文.

但是,使用运行任务

Task.detached(priority: .background) {
//
}

给出如下输出:

handler : <_NSMainThread: 0x600003a28780>{number = 1, name = main}
task : <NSThread: 0x600003a6fe80>{number = 8, name = (null)}
load : <NSThread: 0x600003a6fe80>{number = 8, name = (null)}
result : <NSThread: 0x600003a7d100>{number = 6, name = (null)}

handler 在主队列上运行,然后 taskload 在其他队列上运行,然后有趣的是 resultloadWeather() 返回后再次处于完全不同的状态。

毕竟,在回答你的问题“如果我使用 .detach 作为任务并在我的代码中删除 MainActor.run ,为什么我没有看到崩溃?”,大概是因为您的 ContentView 是一种值类型,因此是线程安全的。如果您将 @State 属性移至 WeatherLoader 中的 @Published,那么您将收到后台线程警告。

关于SwiftUI 安全更新任务的状态变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75158597/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com