如何在SwiftUI中使用Swift Async/Await通过按下按钮的后台操作更新Core Data和UI

snz8szmq  于 2023-06-21  发布在  Swift
关注(0)|答案(1)|浏览(88)

我已经阅读了关于这个主题的其他问题和答案,但似乎找不到一个解决方案,使这个工作在我的情况。我的具体情况是,我有一个按钮,用户按下它可以启动一些异步工作,比如API调用。在这个异步工作完成后,一个Core Data对象,以及引用该对象的UI,需要用新检索到的数据进行更新,但我不知道需要设置的确切方式。下面是我目前的尝试:

@ObservedObject data: MyDataObject // Core Data entity passed in from an upper view
let managedObjectContext = DataController.shared.context // Core Data NSPersistentContainer singleton

var body: some View {
  VStack {
    Text(data.info)

    Button("Tap") {
      getResponseFromNetworkAPI(using: data)
    }
  }
}

func getResponseFromNetworkAPI(using data: MyDataObject) {
  // Do work that should be in the background. Once the response is fetched, I want to
  // update my Core Data entity on the MainActor (which I believe is best practice).
  Task.detached(priority: .userInitiated) {
    var response: String? = nil
    response = await APIServiceClass.requestResponse(using: data)

    await MainActor.run {
       // Error here on the below line:
       // "Reference to capture var 'response' in concurrently-executing code"
       data.info = response
       try? managedObjectContext.save()
    }
  }
}

我的直觉是,我可以从后台任务调度MainActor任务,但我不确定如何正确地将数据传递到它,因为我不允许引用后台任务中检索的数据。我的特定设置可能会有一个修复,但我也很想知道一个最佳实践方法来做到这一点。
APIServiceClass.requestResponse(using:)函数是一个异步函数,它从一些网络调用返回一个String?

h79rfbju

h79rfbju1#

这就是我要做的,看看评论。

struct SampleFlowView: View {
    @Environment(\.managedObjectContext) var context
    @ObservedObject var data: MyDataObject
    @State private var gettingResponse: Bool = false
    var body: some View {
        VStack {
            Text(data.info ?? "no info")
            
            Button {
                gettingResponse.toggle() //Use the Button to trigger a .task
            } label: {
                Text("Tap").overlay {
                    if gettingResponse {
                        ProgressView() //Show a Progress View when the task is running
                    }
                }
            }
            .disabled(gettingResponse) //Disable the button to prevent duplicate calls
            .task(id: gettingResponse) { // async await task
                guard gettingResponse else {
                    return
                }
                print("updating")
                await getResponseFromNetworkAPI(using: data) //Run your method
                
                gettingResponse = false //Let the UI know you ae done
                print("done")
            }
        }
    }
    
    func getResponseFromNetworkAPI(using data: MyDataObject) async {
        //Set the Response String when the long APi call returns
        let responseString = await Task.detached(priority: .userInitiated) {
            try? await Task.sleep(for: .seconds(2)) //Mimicking a delay
            //Mimic returning a String
            return "test \((0...100).randomElement()!)"//await APIServiceClass.requestResponse(using: data)
        }.value
        
        //Use Core Data Concurrency
        await context.perform { //Use async await to let the "actor" determine how to save the context
            
            data.info = responseString
            try? context.save()
        }
    }
}

相关问题