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

zxc*_*rgo 7 button async-await swift swiftui swift-concurrency

我想单击 SwiftUI 中的一个按钮来触发 JSON 编码操作。此操作非常耗时,因此我需要它是异步的。我已经尝试了两种解决方案,但它们不起作用。一个主要问题是如何创建异步版本的 json 编码?

\n

解决方案1)

\n
public func encodeJSON<T>(_ value: T, encoder: JSONEncoder, completionHandler: @escaping (Data?, Error?) -> Void) where T: Encodable {\n        DispatchQueue.global().async {\n            do {\n                let data = try encoder.encode(value)\n                DispatchQueue.main.async {\n                    completionHandler(data, nil)\n                    print("finish encode json")\n                }\n            } catch {\n                DispatchQueue.main.async {\n                    completionHandler(nil, error)\n                    print("fail encode json")\n                }\n            }\n        }\n    }\n    \n    public func encodeJSON<T>(_ value: T, encoder: JSONEncoder) async throws -> Data where T: Encodable {\n        \n        try await withUnsafeThrowingContinuation { continuation in\n            encodeJSON(value, encoder: encoder) { data, error in\n                if let error = error {\n                    continuation.resume(throwing: error)\n                } else if let data = data {\n                    continuation.resume(returning: data)\n                } else {\n                    fatalError()\n                }\n            }\n        }\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
Button {\n                \n                let localDate = dailyUploadRecord.date!\n                let impactMed = UIImpactFeedbackGenerator(style: .medium)\n                impactMed.impactOccurred()\n \n                \n               \n                guard let file = UploadFileManager.shared.fetchedResults else { return }\n                \n                let encoder = JSONEncoder()\n                encoder.dateEncodingStrategy = .millisecondsSince1970\n                \n                Task {\n                    isEncoding = true\n                    let result = try await uploadManager.encodeJSON(file, encoder: encoder)\n                    print(result)\n                    isEncoding = false\n                }\n\n            } label: {\n                Text(\xe2\x80\x9cTEST")\n                    .overlay {\n                        if isEncoding {\n                            ProgressView()\n                        }\n                    }\n            }\n            .disabled(isEncoding)\n            .buttonStyle(.bordered)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
public func encodeJSON<T>(_ value: T, encoder: JSONEncoder) async throws -> Data where T: Encodable {\n        return try encoder.encode(value)\n    }\n
Run Code Online (Sandbox Code Playgroud)\n
Button {\n                \n            let localDate = dailyUploadRecord.date!\n            let impactMed = UIImpactFeedbackGenerator(style: .medium)\n            impactMed.impactOccurred()\n                \n            guard let file = UploadFileManager.shared.fetchedResults else { return }\n                \n            let encoder = JSONEncoder()\n            encoder.dateEncodingStrategy = .millisecondsSince1970\n                \n            Task {\n                isEncoding = true\n                let result = try await encodeJSON(file, encoder: encoder)\n                print(result)\n                isEncoding = false\n            }\n\n        } label: {\n            Text(\xe2\x80\x9cTEST")\n                .overlay {\n                    if isEncoding {\n                        ProgressView()\n                    }\n                }\n        }\n        .disabled(isEncoding)\n        .buttonStyle(.bordered)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n

我尝试了两种解决方案。一种是从 DispatchQueue.global().async {} 创建异步版本并进行转换。另一种是直接将 JSONEncoder().encode(value: Data) 包装在异步函数中。然而,这两种解决方案都没有奏效。

\n

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

\n

mal*_*hal 7

第二种方法是正确的。问题很可能是编码器抛出异常,当数据中的某些内容无法编码时就会发生该异常。这意味着isEncoding = false线路未到达,UI 停留在编码状态。像这样修复它:

.task(id: isEncoding) {
    if isEncoding == false {
       return
    }
       do {
           let result = try await encodeJSON(file, encoder: encoder)
            print(result)
        }
        catch {
           print(error.localizedDescription)
        }
        isEncoding = false
     }
Run Code Online (Sandbox Code Playgroud)