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

Swift调用API偶发400错误,重试1-3次恢复问题求助

Swift API调用偶尔返回400错误的排查与修复

看起来你遇到了个挺棘手的问题:同一个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:17:41