Swift调用API偶发400错误,重试1-3次恢复问题求助
看起来你遇到了个挺棘手的问题:同一个API用Postman和PHP调用都稳得一批,但Swift里偶尔会蹦出400错误,还只有空白响应头,重试1-3次又能正常返回。我帮你拆解下可能的核心原因,再给你具体的修复方案:
可能的问题根源分析
1. 美化版JSON触发API校验失败
你在序列化请求体时用了JSONSerialization.WritingOptions.prettyPrinted,这个选项会生成带换行、缩进的“美化版”JSON。但很多API对请求体格式要求很严格,Postman和PHP默认发的是压缩后的无空格JSON,这种格式差异可能导致API没法正确解析请求,直接返回400。
2. 复用旧Timestamp被判定为无效请求
你在process方法里只生成一次timestamp,重试时用的还是同一个值。有些API会把重复的timestamp当作重复请求或者过期请求,直接打回400。而Postman每次请求都会生成新的timestamp,自然不会踩这个坑。
3. 复用NSMutableURLRequest引发状态污染
你重试时直接复用了原来的NSMutableURLRequest实例,URLSession可能会缓存这个请求的一些状态(比如已有的头信息、请求体残留),导致重试的请求并不是完全干净的新请求,进而触发错误。
4. 未显式设置Content-Type请求头
虽然JSON序列化可能会隐式处理,但有些API要求必须显式设置Content-Type: application/json才能正确解析请求。Postman会自动加这个头,而你的Swift代码里没设置,这也可能是问题之一。
具体修复方案
1. 移除JSON美化选项,用标准压缩格式
把JSONSerialization.WritingOptions.prettyPrinted换成空选项,生成符合API预期的压缩JSON:
request.httpBody = try JSONSerialization.data(withJSONObject: params, options: [])
2. 每次请求都生成新的Timestamp
把timestamp的生成逻辑移到sendRequest方法里,确保每次请求(包括重试)都用最新的时间戳:
// 在sendRequest中动态更新timestamp var mutableParams = params if method != .GET { mutableParams["timestamp"] = Int(Date().timeIntervalSince1970) } request.httpBody = try JSONSerialization.data(withJSONObject: mutableParams, options: [])
3. 每次重试都创建全新的请求对象
不要复用NSMutableURLRequest,而是把构建请求的必要参数(url、method、token等)传入sendRequest,每次都新建请求:
// 重构sendRequest方法,不再传入现成的request func sendRequest(session: URLSession, url: String, method: ApiMethod, token: String?, params: [String:Any], pendingWriteId: String?, initializing: Bool, retryCount: Int = 0, completion: @escaping (Error?, JSON) -> Void) { guard let requestUrl = URL(string: url) else { completion(ApiError.invalidURL, JSON()) return } var request = NSMutableURLRequest(url: requestUrl) request.httpMethod = method.rawValue // 显式设置Content-Type头,对齐Postman/PHP的请求格式 request.setValue("application/json", forHTTPHeaderField: "Content-Type") if let token = token { request.setValue(token, forHTTPHeaderField: "token") } // 每次请求都更新timestamp var mutableParams = params if method != .GET { mutableParams["timestamp"] = Int(Date().timeIntervalSince1970) } do { request.httpBody = try JSONSerialization.data(withJSONObject: mutableParams, options: []) let task = session.dataTask(with: request as URLRequest) { data, response, error in DispatchQueue.main.async { guard let httpResponse = response as? HTTPURLResponse else { completion(error ?? ApiError.noResponse, JSON()) return } let statusCode = httpResponse.statusCode if statusCode == 200 { guard let d = data else { completion(ApiError.noData, JSON()) return } do { let json = try JSON(data: d) completion(nil, json) } catch { completion(error, JSON()) } } else { print("\(url) status code = \(statusCode)") // 限制重试次数,避免无限递归 if retryCount < 3 { self.sendRequest(session: session, url: url, method: method, token: token, params: params, pendingWriteId: pendingWriteId, initializing: initializing, retryCount: retryCount + 1, completion: completion) } else { completion(ApiError.maxRetriesExceeded, JSON()) } } } } task.resume() } catch { print("Oops something happened") completion(ApiError.willNotResolve, JSON()) } }
4. 给重试加次数限制
上面的重构代码里已经加了retryCount参数,限制最多重试3次,避免因为API持续故障导致无限递归。
最后调整process方法的调用
修改process里调用sendRequest的代码,传入必要参数而非现成的request:
self.sendRequest(session: session, url: url, method: method, token: auth.authToken, params: params, pendingWriteId: pendingWriteId, initializing: initializing) { (error, json) in completion(error, json) }
这些改动本质上是让Swift发送的请求和Postman/PHP的请求在格式、参数上完全对齐,消除API的校验差异,应该能解决偶尔出现的400问题。
内容的提问来源于stack exchange,提问作者Sean Cook

