让我说,我是在 SwiftUI 中使用 UI 的新手,因此,如果在其他各种情况下可能已对此问题进行了相邻回答,我深表歉意,但我似乎找不到特定于我的问题的问题。
我创建了一个为 MacOS 生成 CGImage 的函数。它在我最初打算使用的命令行工具中运行得非常好。无需赘述,它是异步的,因为它使用 withTaskGroup() 来减少生成图像所需的工作时间。
我想在 @ObservableObject 类中使用它的 @Published var。这是我从非异步版本开始的基本代码:
struct ContentView: View {
@StateObject var target = Target()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button("Hello, world! \(target.value)")
{
target.value += 1
if (target.value == 3)
{
target.value = 0
}
target.update()
}
target.image
}
.padding()
}
}
class Target: ObservableObject
{
@Published var image: Image
@Published var value: Int
init()
{
value = 0
image = Target.update(input: 0)
}
func update()
{
image = Target.update(input: value)
}
static func update(input: Int) -> Image
{
var cgimage: CGImage?
cgimage = drawMyImage(input: input)
return Image(cgimage!, scale: 1.0, label: Text("target \(input)"))
}
}
如果有人对快速测试感兴趣,我在下面放置了一个虚拟的drawMyImage()函数。
所以上面的代码不是异步的。问题是当我开始使用异步时,所以我异步了drawMyImage()函数,这就是我真正的drawImage()函数:
func drawMyImage(input: Int) async -> CGImage?
不支持并发的自动闭包中的“异步”调用
如果我在堆栈上插入适当的异步/等待:
init() async
{
value = 0
image = await Target.update(input: 0)
}
func update() async
{
image = await Target.update(input: value)
}
static func update(input: Int) async -> Image
{
var cgimage: CGImage?
cgimage = await drawMyImage(input: input)
我最终在 ContentView 中得到了这个我似乎无法解决的问题:
struct ContentView: View {
@StateObject var target = Target() //'async' call in an autoclosure that does not support concurrency
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button("Hello, world! \(target.value)")
{
target.value += 1
if (target.value == 3)
{
target.value = 0
}
target.update() //'async' call in an autoclosure that does not support concurrency
并发执行代码中捕获的 var 'myImage' 发生变化
所以我尝试仅将drawMyImage()函数包装在任务中,但效果不太好:
static func update(input: Int) -> Image
{
var myImage: Image
Task
{
var cgimage: CGImage?
cgimage = await drawMyImage(input: input)
myImage = Image(cgimage!, scale: 1.0, label: Text("target \(input)")) //Mutation of captured var 'myImage' in concurrently-executing code
}
return myImage
}
在同步非隔离上下文中调用主参与者隔离的静态方法“update(input:)”
所以我尝试添加 @MainActor 装饰器,这给了我一个不同的错误:
init()
{
value = 0
image = Target.update(input: 0) //Call to main actor-isolated static method 'update(input:)' in a synchronous nonisolated context
}
func update()
{
image = Target.update(input: value) //Call to main actor-isolated static method 'update(input:)' in a synchronous nonisolated context
}
@MainActor
static func update(input: Int) -> Image
{
我已经尝试了其他几件事来解决这个问题,但这似乎是我一遍又一遍地解决的主要问题。如果有人能帮我一把,我将不胜感激。预先感谢。
虚拟drawMyImage()函数
//dummy test function with no real input, no real async
//remove 'async' for how it's supposed to work
func drawMyImage(input: Int) async -> CGImage? {
let bounds = CGRect(x: 0, y:0, width: 200, height: 200)
let intWidth = Int(ceil(bounds.width))
let intHeight = Int(ceil(bounds.height))
let bitmapContext = CGContext(data: nil,
width: intWidth, height: intHeight,
bitsPerComponent: 8,
bytesPerRow: (((intWidth * 4) + 15) / 16) * 16,
space: CGColorSpace(name: CGColorSpace.sRGB)!,
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
if let cgContext = bitmapContext {
cgContext.saveGState()
if input == 0
{
cgContext.setFillColor(red: 255, green: 0, blue: 0, alpha: 255)
}
else if input == 1
{
cgContext.setFillColor(red: 0, green: 255, blue: 0, alpha: 255)
}
else
{
cgContext.setFillColor(red: 0, green: 0, blue: 255, alpha: 255)
}
cgContext.fill(bounds)
cgContext.restoreGState()
return cgContext.makeImage()
}
return nil
}
就像第一次尝试一样,您可以在调用链中传播
async
,但不能直到 init
。因为该操作是异步的,所以 Target
不会 在初始化后立即拥有可用的 Image
。另外,正如您在尝试中发现的那样,该视图需要 ObservableObject
同步初始化。
您应该将
image
设为可选:
class Target: ObservableObject
{
@Published var image: Image?
@Published var value: Int = 0
@MainActor // isolate this to the main actor because @Published vars can only be updated from the main thread
func update() async
{
image = await Target.update(input: value)
}
static func update(input: Int) async -> Image
{
var cgimage: CGImage?
cgimage = await drawMyImage(input: input)
return Image(cgimage!, scale: 1.0, label: Text("target \(input)"))
}
}
task(id:)
的绝佳机会。当视图首次出现以及其 id
参数更改时,这会运行异步操作。只需传递 target.value
作为 ID,SwiftUI 就会为您处理所有任务取消。
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button("Hello, world! \(target.value)")
{
target.value += 1
if (target.value == 3)
{
target.value = 0
}
}
target.image
}
.padding()
.task(id: target.value) {
await target.update()
}