Swift实现关键消息日志POST请求的技术问题求助
嘿,你遇到的这两个问题我之前做类似日志功能时也踩过坑,咱们一步步来解决~
解决方案:异步消息队列+正确的网络请求实现
问题1:异步请求下的消息队列管理
原来的同步循环思路肯定行不通,因为异步请求发出去后没法立刻拿到结果,而且请求完成的顺序可能和发送顺序完全不一样。这里给你一套经过实践验证的改进方案:
核心思路
- 给每个消息分配唯一ID(比如UUID),哪怕异步返回结果,也能精准定位要移除的消息
- 维护一个「正在发送」的集合,避免同一条消息被重复发送
- 用串行队列处理所有队列操作,保证线程安全(UserDefaults和集合操作不会被多线程打乱)
- 消息发送失败后保留在队列中,下次添加新消息或App启动时自动重试
完整代码实现
import Foundation class MessageLogger { private static let defaults = UserDefaults.standard private static let backupQueueKey = "MessagesQueue" // 串行队列,保证所有队列操作线程安全 private static let sendingQueue = DispatchQueue(label: "com.yourapp.messagelogger.sending") // 记录正在发送的消息ID,防止重复发送 private static var inFlightMessageIDs = Set<String>() public static func log(message: String) { sendingQueue.async { // 1. 读取本地存储的消息队列,不存在就初始化空数组 var messageQueue = defaults.array(forKey: backupQueueKey) as? [[String: Any]] ?? [] // 2. 给新消息生成唯一ID,存入队列 let messageID = UUID().uuidString let newMessage = ["id": messageID, "content": message] messageQueue.append(newMessage) // 3. 保存更新后的队列到UserDefaults defaults.set(messageQueue, forKey: backupQueueKey) // 4. 尝试发送所有未在发送的消息 attemptSendingMessages() } } private static func attemptSendingMessages() { sendingQueue.async { var messageQueue = defaults.array(forKey: backupQueueKey) as? [[String: Any]] ?? [] // 过滤出还没在发送的消息 let pendingMessages = messageQueue.filter { guard let id = $0["id"] as? String else { return false } return !inFlightMessageIDs.contains(id) } for message in pendingMessages { guard let id = message["id"] as? String, let content = message["content"] as? String else { continue } // 标记这条消息正在发送 inFlightMessageIDs.insert(id) // 异步发送消息,完成后通过闭包返回结果 sendOff(message: content) { success in self.sendingQueue.async { // 发送完成后移除「正在发送」标记 self.inFlightMessageIDs.remove(id) if success { // 从队列中删除成功发送的消息 messageQueue.removeAll { $0["id"] as? String == id } self.defaults.set(messageQueue, forKey: self.backupQueueKey) // 继续尝试发送剩下的消息 self.attemptSendingMessages() } else { print("消息 \(id) 发送失败,稍后会重试") // 失败不用做额外操作,下次触发log或App启动时会自动重试 } } } } } } private static func sendOff(message: String, completion: @escaping (Bool) -> Void) { // 这里替换成你的网络请求逻辑,请求完成后调用completion告知结果 guard let requestURL = buildRequestURL(message: message) else { completion(false) return } let task = URLSession.shared.dataTask(with: requestURL) { data, response, error in if let error = error { print("请求出错:\(error.localizedDescription)") completion(false) return } // 验证响应状态码是否成功(200-299) guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { print("响应无效或状态码错误") completion(false) return } // 服务器返回成功,标记消息发送完成 completion(true) } task.resume() } }
问题2:Swift中模拟浏览器的URL请求
你说浏览器里敲URL能成功,但Swift里就不行还超时,这大概率是URL参数编码不对或者请求方式没匹配上服务器要求。
核心问题分析
浏览器会自动对URL中的特殊字符(比如空格、引号)做URL编码,而如果直接在Swift里拼接字符串,会导致参数格式错误,服务器无法解析请求,最终超时。
正确实现方式
用URLComponents和URLQueryItem构建请求URL,它们会自动处理参数编码,和浏览器的行为完全一致:
extension MessageLogger { private static func buildRequestURL(message: String) -> URL? { var components = URLComponents(string: "http://coolSite.org/Debug.php") // 添加query参数,自动处理编码 components?.queryItems = [ URLQueryItem(name: "message", value: message) ] return components?.url } }
如果服务器接受POST请求怎么办?
如果你的Debug.php实际是接收POST参数,那可以修改sendOff方法如下:
private static func sendOff(message: String, completion: @escaping (Bool) -> Void) { guard let url = URL(string: "http://coolSite.org/Debug.php") else { completion(false) return } var request = URLRequest(url: url) request.httpMethod = "POST" // 设置正确的Content-Type,和表单提交格式一致 request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") // 编码POST参数 let parameters = ["message": message] let parameterString = parameters.map { let encodedValue = $0.value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! return "\($0.key)=\(encodedValue)" }.joined(separator: "&") request.httpBody = parameterString.data(using: .utf8) let task = URLSession.shared.dataTask(with: request) { data, response, error in // 同之前的响应处理逻辑 if let error = error { print("请求出错:\(error.localizedDescription)") completion(false) return } guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { print("响应无效或状态码错误") completion(false) return } completion(true) } task.resume() }
内容的提问来源于stack exchange,提问作者J.Doe
相关产品推荐
相关产品推荐

