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() }
问题分析
- 语法错误:completion回调的参数定义不符合Swift规范,导致编译失败
- 过时的异步方式:completion回调在Swift 5.5+已被async/await取代,后者更简洁易维护
- 线程问题:网络请求回调在后台线程执行,直接修改
@Published变量可能导致UI更新异常 - 崩溃风险:强制解包
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
相关产品推荐
相关产品推荐

