如何使用SwiftUI按钮执行异步操作

7
我想在SwiftUI中点击一个按钮,触发JSON编码操作。由于此操作耗时,因此我需要它是异步的。我已经尝试了两个解决方案,但它们都不起作用。一个主要问题是如何创建JSON编码的异步版本?
解决方案1)
public func encodeJSON<T>(_ value: T, encoder: JSONEncoder, completionHandler: @escaping (Data?, Error?) -> Void) where T: Encodable {
        DispatchQueue.global().async {
            do {
                let data = try encoder.encode(value)
                DispatchQueue.main.async {
                    completionHandler(data, nil)
                    print("finish encode json")
                }
            } catch {
                DispatchQueue.main.async {
                    completionHandler(nil, error)
                    print("fail encode json")
                }
            }
        }
    }
    
    public func encodeJSON<T>(_ value: T, encoder: JSONEncoder) async throws -> Data where T: Encodable {
        
        try await withUnsafeThrowingContinuation { continuation in
            encodeJSON(value, encoder: encoder) { data, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else if let data = data {
                    continuation.resume(returning: data)
                } else {
                    fatalError()
                }
            }
        }
    }

然后我在SwiftUI的主体中调用该函数:

Button {
                
                let localDate = dailyUploadRecord.date!
                let impactMed = UIImpactFeedbackGenerator(style: .medium)
                impactMed.impactOccurred()
 
                
               
                guard let file = UploadFileManager.shared.fetchedResults else { return }
                
                let encoder = JSONEncoder()
                encoder.dateEncodingStrategy = .millisecondsSince1970
                
                Task {
                    isEncoding = true
                    let result = try await uploadManager.encodeJSON(file, encoder: encoder)
                    print(result)
                    isEncoding = false
                }

            } label: {
                Text(“TEST")
                    .overlay {
                        if isEncoding {
                            ProgressView()
                        }
                    }
            }
            .disabled(isEncoding)
            .buttonStyle(.bordered)

然而,它给了我运行时错误:Thread 6: EXC_BREAKPOINT (code=1, subcode=0x1b338b088)

然后,我尝试了第二个解决方案:

public func encodeJSON<T>(_ value: T, encoder: JSONEncoder) async throws -> Data where T: Encodable {
        return try encoder.encode(value)
    }

Button {
                
            let localDate = dailyUploadRecord.date!
            let impactMed = UIImpactFeedbackGenerator(style: .medium)
            impactMed.impactOccurred()
                
            guard let file = UploadFileManager.shared.fetchedResults else { return }
                
            let encoder = JSONEncoder()
            encoder.dateEncodingStrategy = .millisecondsSince1970
                
            Task {
                isEncoding = true
                let result = try await encodeJSON(file, encoder: encoder)
                print(result)
                isEncoding = false
            }

        } label: {
            Text(“TEST")
                .overlay {
                    if isEncoding {
                        ProgressView()
                    }
                }
        }
        .disabled(isEncoding)
        .buttonStyle(.bordered)

然而,用户界面被冻结,当encodeJSON完成后,它恢复正常,我可以与之交互。

我的问题是:如何创建一个JSONEncoder().encode(value: Data)的异步版本,并在SwiftUI按钮中调用它,而不会阻塞主线程(使用户界面冻结)?欢迎任何建议!

我尝试了两个解决方案。一个是从DispatchQueue.global().async {}创建一个异步版本并将其转换。另一个是直接将JSONEncoder().encode(value: Data)包装在异步函数中。但是,这两个解决方案都没有起作用。

我希望单击按钮后,相关的编码函数可以异步执行。


使用一个**视图模型(View Model)**,一个ObervableObject并在那里完成繁重的工作。而不是使用DispatchQueue和完成处理程序,使用async/await - vadian
谢谢您的评论。您能否提供更多细节?如何让视图模型在不阻塞用户界面的情况下完成繁重的工作? - zxcheergo
你需要更深入地了解@MainActorTask { @MainActor in …}
  1. 你需要使用@MainActor标记异步方法,以便在主要执行者中高优先级地执行任务,而不是在后台优先级中执行。
  2. 创建一个本地同步函数来执行异步工作,如下所示: func encodeJSON() { Task { @MainActor in await viewModel.encodeJSON(_ value:)
- Nick Rossik
这个回答解决了你的问题吗?[在后台线程(并发)中运行异步函数时遇到问题] (https://dev59.com/aHkPtIcB2Jgan1znj1Pc) - lorem ipsum
1个回答

7
第二种方式是正确的。问题可能是编码器抛出了一个异常,当数据中的某些内容无法被编码时会发生这种情况。这意味着isEncoding = false行没有被执行,导致用户界面一直停留在编码状态。请按照以下方式进行修复:
.task(id: isEncoding) {
    if isEncoding == false {
       return
    }
       do {
           let result = try await encodeJSON(file, encoder: encoder)
            print(result)
        }
        catch {
           print(error.localizedDescription)
        }
        isEncoding = false
     }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接