You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中POST请求异步更新UI问题求助(2023正确实现)

SwiftUI聊天应用网络请求的现代化实现(2023)

问题背景

开发了一款由带@Published变量的GlobalAppState环境对象驱动UI的聊天应用,需要发起HTTP POST请求并将结果添加到会话中以正确更新视图。原本使用completion回调方式实现时出现错误,且该方式已过时,需要2023年的正确实现方案。

原错误代码

调用部分

struct MessageInputView: View {
    @EnvironmentObject var appState: GlobalAppState
    @State var message: ChatMessage = ChatMessage.empty
    
    var body: some View {
        HStack{
            TextField("Your Message", text: $message.message, axis: .vertical)
                .lineLimit(5)
            Button("Send"){
                let tmpMsg = message
                message = ChatMessage.empty
                // 准备会话历史
                var history = ""
                for conversationMessage in appState.conversation.messages
                {
                    history += ("\n" + conversationMessage.message)
                }
                // 将当前消息添加到会话
                appState.conversation.messages.append(message)
                
                // 调用OpenAI请求,尝试用task包裹异步逻辑
                doChatRequest(message : tmpMsg, history : history, finished { response, responseError in
                    // 将OpenAI的响应添加到会话
                    appState.conversation.messages.append(response)
                })
            } // SEND按钮结束
        }
        .padding()
    }
}

请求处理函数

func doChatRequest (message: ChatMessage, history : String, finished: ((response: ChatMessage, responseError: String)) -> Void)
{
    let botActor = ChatActor(name:"bot", isMe: false)

    // 准备请求
    let url = URL(string: "http://localhost:5117/api/v1/OpenAI/chat")!
    var request = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"

    var botResponse = ChatMessage.empty

    let oaiRequest = OpenAIRequest(request: message.message, history: history)
    
    do {
        try request.httpBody = JSONEncoder().encode(oaiRequest)
    } catch {
        finished(ChatMessage(actor: botActor, date: Date.now, message: "Error converting message into sendable object.") )
    }
    
    let task = URLSession.shared.dataTask(with: request){ data, response, error in
        if let error = error {
            finished(ChatMessage(actor: botActor, date: Date.now, message: "Error"))
        }
        let response = try? JSONDecoder().decode(OpenAIResponse.self, from: data!)
        if let chatResponse = response {
            finished(ChatMessage(actor: botActor, date: Date.now, message: chatResponse.response))
        }
        else
        {
            finished(ChatMessage(actor: botActor, date: Date.now, message: "Error has occurred."))
        }
    }
    
    task.resume()
}

问题分析

  1. 语法错误:completion回调的参数定义不符合Swift规范,导致编译失败
  2. 过时的异步方式:completion回调在Swift 5.5+已被async/await取代,后者更简洁易维护
  3. 线程问题:网络请求回调在后台线程执行,直接修改@Published变量可能导致UI更新异常
  4. 崩溃风险:强制解包data!会在数据为空时引发崩溃

正确实现方案(2023)

步骤1:重构网络请求为async/await风格

将请求函数改为异步函数,使用async throws处理错误,返回ChatMessage:

func doChatRequest(message: ChatMessage, history: String) async throws -> ChatMessage {
    let botActor = ChatActor(name: "bot", isMe: false)
    
    guard let url = URL(string: "http://localhost:5117/api/v1/OpenAI/chat") else {
        throw NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的请求地址"])
    }
    
    var request = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    
    let oaiRequest = OpenAIRequest(request: message.message, history: history)
    request.httpBody = try JSONEncoder().encode(oaiRequest)
    
    let (data, response) = try await URLSession.shared.data(for: request)
    
    // 检查HTTP响应状态码
    guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
        throw NSError(domain: "HTTPError", code: -2, userInfo: [NSLocalizedDescriptionKey: "请求失败"])
    }
    
    // 解码响应数据
    guard let chatResponse = try? JSONDecoder().decode(OpenAIResponse.self, from: data) else {
        throw NSError(domain: "DecodeError", code: -3, userInfo: [NSLocalizedDescriptionKey: "响应数据解析失败"])
    }
    
    return ChatMessage(actor: botActor, date: Date.now, message: chatResponse.response)
}

步骤2:修改按钮的异步调用逻辑

在SwiftUI的Button动作中使用Task包裹异步代码,并确保在主线程更新UI:

struct MessageInputView: View {
    @EnvironmentObject var appState: GlobalAppState
    @State var message: ChatMessage = ChatMessage.empty
    
    var body: some View {
        HStack{
            TextField("Your Message", text: $message.message, axis: .vertical)
                .lineLimit(5)
            Button("Send"){
                let tmpMsg = message
                message = ChatMessage.empty
                
                // 准备会话历史
                let history = appState.conversation.messages
                    .map { $0.message }
                    .joined(separator: "\n")
                
                // 立即添加用户消息到会话(更新UI)
                DispatchQueue.main.async {
                    appState.conversation.messages.append(tmpMsg)
                }
                
                // 异步调用网络请求
                Task {
                    do {
                        let botResponse = try await doChatRequest(message: tmpMsg, history: history)
                        // 回到主线程更新UI
                        DispatchQueue.main.async {
                            appState.conversation.messages.append(botResponse)
                        }
                    } catch {
                        let errorMessage = ChatMessage(
                            actor: ChatActor(name: "bot", isMe: false),
                            date: Date.now,
                            message: error.localizedDescription
                        )
                        DispatchQueue.main.async {
                            appState.conversation.messages.append(errorMessage)
                        }
                    }
                }
            }
        }
        .padding()
    }
}

额外注意事项

  • 确保GlobalAppState中的conversation.messages被标记为@Published,这样UI才能自动更新:
    class GlobalAppState: ObservableObject {
        @Published var conversation: Conversation = Conversation()
        // 其他属性...
    }
    
  • 为了符合Swift并发要求,ChatMessage、ChatActor、OpenAIRequest、OpenAIResponse等模型需要遵循Sendable协议:
    struct ChatMessage: Codable, Sendable {
        // 属性定义...
    }
    

内容的提问来源于stack exchange,提问作者David Crook

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 10:42:49