在Swift UI中,在异步函数完成后返回到上一页。

huangapple go评论52阅读模式
英文:

Go back to previous page in Swift UI after Async func completion

问题

我有一个视图,其中包含以下代码:

@Environment(\.presentationMode) var presentationMode

...

Button {
    Task {
        await asyncFunction(data: data) {
            self.presentationMode.wrappedValue.dismiss()
        }
    }
} label: {
    Text("Save")
}

而我的异步函数如下所示:

func asyncFunction(data: Data, completion: @escaping () -> Void) async {
  
  // 准备 URL
  let url = URL(string: "api-url")
  guard let requestUrl = url else { fatalError() }

  // 将数据编码为 HTTP 请求的负载
  let encodedData = try! JSONEncoder().encode(data)

  // 执行 HTTP 请求
  let (responseData, _) = try! await URLSession.shared.data(for: URLRequest(url: requestUrl, httpMethod: "POST", httpBody: encodedData))

  // 将 HTTP 响应数据转换为字符串
  if let responseData = Optional(responseData) {
      if let dataString = String(data: responseData, encoding: .utf8) {
          print("Response:\n \(dataString)")
      }
  }

  // 调用完成处理程序
  completion()
}

问题在于,一旦请求完成并且该调用dismiss()的时候,应用程序会崩溃并显示以下错误:“Thread 18: EXC_BREAKPOINT (code=1, subcode=0x11022cd24)”。

看起来是与闭包有关的问题,但不确定具体原因是什么。

英文:

I have a view with the following code

@Environment(\.presentationMode) var presentationMode
          
  ...

 Button {
    Task {
       await asyncFunction(data:data) {                            
          self.presentationMode.wrappedValue.dismiss()
       }
    }
 } label: {
   Text("Save")
 }

And my async function goes as follow:

func asyncFunction(data: Data, completion: @escaping () -> Void) async {
  
  // Prepare URL
  let url = URL(string: "api-url")
  guard let requestUrl = url else { fatalError() }

  // HTTP Request payload which will be sent in HTTP Request Body
  let d = try! JSONEncoder().encode(data)

  ...

  // Perform HTTP Request
  let (responseData, _) = try! await URLSession.shared.data(for: request)

  // Convert HTTP Response Data to a String
     if let responseData = Optional(responseData) {
         if let dataString = String(data: responseData, encoding: .utf8) {
             print("Response:\n \(dataString)")
         }
     }

  // Call the completion handler
  completion()
}

The problem is that, once the the request is complete and it's time to call the dismiss(), the app crashes with this error
Thread 18: EXC_BREAKPOINT (code=1, subcode=0x11022cd24).

Looks like a problem with the closure, but not sure to understand what is it?

答案1

得分: 1

Here is the translation of the provided code:

  1. 浮动的 Task 就像你在 Button 中所使用的,传统上被认为是不良实践,因为你失去了对 Task 本身的控制。

  2. 当采用 async await 时,完成处理程序已经成为过去,你应该永远(极少例外,我只知道一个例外)不要从 async await 转到完成处理程序。

如果必须从完成处理程序转换到 async await,则必须使用 CheckedContinuation

由于 async await 的行为就像是同步的,你可以完全删除完成处理程序。

Button {
    Task {
        await asyncFunction(data: data)
        self.presentationMode.wrappedValue.dismiss()
    }
} label: {
    Text("保存")
}

func asyncFunction(data: Data) async {
    // 准备 URL
    let url = URL(string: "api-url")
    guard let requestUrl = url else { fatalError() }

    // HTTP 请求载荷将在 HTTP 请求正文中发送
    let d = try! JSONEncoder().encode(data)

    // 执行 HTTP 请求
    let (responseData, _) = try! await URLSession.shared.data(for: requestUrl)

    // 将 HTTP 响应数据转换为字符串
    if let responseData = Optional(responseData) {
        if let dataString = String(data: responseData, encoding: .utf8) {
            print("响应:\n \(dataString)")
        }
    }

    // 不要调用完成处理程序
}

现在要摆脱 Task,你应该使用 .task

struct AsyncButton: View {
    @Environment(\.dismiss) var dismiss

    @State private var isProcessingFunction: Bool = false
    var body: some View {
        Button {
            isProcessingFunction.toggle()
        } label: {
            Text("保存")
        }.task(id: isProcessingFunction) {
            guard isProcessingFunction else {
                return
            }
            await asyncFunction(data: data)
            dismiss()
            isProcessingFunction = false
        }
    }
}

通过这种方式,你可以充分利用 async await

struct AsyncButton: View {
    @Environment(\.dismiss) var dismiss

    @State private var isProcessingFunction: Bool = false
    var body: some View {
        Button {
            isProcessingFunction.toggle()
        } label: {
            Text("保存")
                .overlay {
                    // 添加 ProgressView
                    if isProcessingFunction {
                        ProgressView()
                    }
                }
        }
        .disabled(isProcessingFunction) // 可能禁用按钮以防止重复调用。或不允许用户取消调用。
        .task(id: isProcessingFunction) {
            guard isProcessingFunction else {
                return
            }
            await asyncFunction(data: data)
            dismiss()
            isProcessingFunction = false
        }
    }
}
英文:

Several issues

  1. Floating Task like what you have in the Button are traditionally considered bad practice because you lose control of the Task itself.

  2. Completion handlers are a thing of the past when adopting async await you should Never (rare exceptions I only know of one) go from async await to a completion handler.

If you have to go from a completion handler to async await you have to use a CheckedContinuation.

Since async await acts as if it is synchronous you can just remove the completion handler completely.

 Button {
    Task {
       await asyncFunction(data:data)
       self.presentationMode.wrappedValue.dismiss()

    }
 } label: {
   Text("Save")
 }

func asyncFunction(data: Data) async {
  
  // Prepare URL
  let url = URL(string: "api-url")
  guard let requestUrl = url else { fatalError() }

  // HTTP Request payload which will be sent in HTTP Request Body
  let d = try! JSONEncoder().encode(data)

  ...

  // Perform HTTP Request
  let (responseData, _) = try! await URLSession.shared.data(for: request)

  // Convert HTTP Response Data to a String
     if let responseData = Optional(responseData) {
         if let dataString = String(data: responseData, encoding: .utf8) {
             print("Response:\n \(dataString)")
         }
     }

  // NO Call the completion handler

}    

Now to get rid of the Task you should use .task

struct AsyncButton: View {
    @Environment(\.dismiss) var dismiss

    @State private var isProcessingFunction: Bool = false
    var body: some View {
        Button {
            isProcessingFunction.toggle()
        } label: {
          Text("Save")
        }.task(id: isProcessingFunction) {
            guard isProcessingFunction else {
                return
            }
            await asyncFunction(data:data)
            dismiss()
            isProcessingFunction = false
        }
    }
}

And with this you can take full advantage of async await

struct AsyncButton: View {
    @Environment(\.dismiss) var dismiss

    @State private var isProcessingFunction: Bool = false
    var body: some View {
        Button {
            isProcessingFunction.toggle()
        } label: {
          Text("Save")
                .overlay {
                    //Add a ProgressView
                    if isProcessingFunction {
                        ProgressView()
                    }
                }
        }
        .disabled(isProcessingFunction) //Maybe disable button to prevent duplicate calls. Or not to allow the user to cancel the call.
        .task(id: isProcessingFunction) {
            guard isProcessingFunction else {
                return
            }
            await asyncFunction(data:data)
            dismiss()
            isProcessingFunction = false
        }
    }
}

huangapple
  • 本文由 发表于 2023年6月26日 22:45:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76557762.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定