Alamofire+PromiseKit并行请求超时异常:180秒设置未生效
使用Alamofire + PromiseKit批量获取物品列表,部分请求耗时较长。已将Alamofire的SessionManager超时时间设为180秒,但首次调用getRemoteItem后60秒,所有未完成请求均进入print("IN FAIL 2")分支,报错如下:
responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(error: Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around line 1, column 0." UserInfo={NSDebugDescription=Invalid value around line 1, column 0., NSJSONSerializationErrorIndex=0}))
getItems方法会并行发起所有物品请求,推测问题和并行有关,但不清楚180秒超时未生效的原因,相关代码如下:
func getItems(ids:[Int]) -> [Promise<Bool>] { let totalCount = ids.count return ids.map { index -> Promise<Bool> in return firstly { getRemoteItem(id: index, totalCount: totalCount) } } } func getRemoteItem(id:Int, totalCount:Int) -> Promise<Bool> { let headers = ["Authorization": "JWT \(userToken)"] return Promise { seal in let url = baseURL + "/api/items" let itemParams: Parameters = ["item_id": id] alamoFireManager!.request(URL(string: url)!, method: .post, parameters: itemParams, encoding: JSONEncoding.default, headers: headers).responseJSON { response in switch response.result { case .success: if let jsonObject = response.result.value { firstly { self.saveItemToLocalStorage(jsonObject: jsonObject as! [String: Any], responseData: response.data!, totalCount: totalCount) }.done { status in seal.fulfill(status) }.catch { error in seal.reject(error) } } else if let error = response.error { print("IN FAIL 1") seal.reject(error) } case .failure(let error): print("IN FAIL 2") seal.reject(error) } } } } private lazy var alamoFireManager: SessionManager? = { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 180 configuration.timeoutIntervalForResource = 180 let alamoFireManager = Alamofire.SessionManager(configuration: configuration) return alamoFireManager }()
核心问题定位
- 超时未生效:自定义的
alamoFireManager是可选类型且懒加载,若初始化时机晚于请求发起,会默认使用Alamofire自带的SessionManager(默认超时60秒),导致自定义配置失效。 - JSON序列化失败:60秒超时后服务器可能返回非JSON格式的错误内容(如HTML错误页),或并行请求过载导致服务器返回空数据,触发序列化失败报错。
具体修复步骤
确保自定义SessionManager生效:
去掉可选类型,强制初始化,避免使用默认实例:private lazy var alamoFireManager: SessionManager = { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 180 configuration.timeoutIntervalForResource = 180 return SessionManager(configuration: configuration) }()调用时直接使用
alamoFireManager.request(...),无需强制解包!,规避nil风险。限制并行请求数量:
默认httpMaximumConnectionsPerHost为6,过多并行请求会导致队列阻塞或服务器限流。根据服务器承载能力调整并发数:configuration.httpMaximumConnectionsPerHost = 10 // 可根据实际情况修改添加失败日志排查:
在IN FAIL 2分支打印原始响应内容,确认服务器返回的实际数据:case .failure(let error): print("IN FAIL 2 - Error: \(error)") if let data = response.data, let responseStr = String(data: data, encoding: .utf8) { print("Server Raw Response: \(responseStr)") } seal.reject(error)先获取原始数据再解析JSON:
替换responseJSON为responseData,手动解析JSON,避免序列化失败直接进入failure分支:alamoFireManager.request(...).responseData { response in switch response.result { case .success(let data): do { let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] firstly { self.saveItemToLocalStorage(jsonObject: jsonObject, responseData: data, totalCount: totalCount) }.done { status in seal.fulfill(status) }.catch { error in seal.reject(error) } } catch { print("JSON解析失败: \(error)") seal.reject(error) } case .failure(let error): print("IN FAIL 2 - 请求失败: \(error)") seal.reject(error) } }优化PromiseKit批量请求管理:
使用when(fulfilled:)统一管理批量Promise,便于统一处理所有请求的完成与失败:func getItems(ids:[Int]) -> Promise<[Bool]> { let promises = ids.map { getRemoteItem(id: $0, totalCount: ids.count) } return when(fulfilled: promises) }
内容的提问来源于stack exchange,提问作者KvnH

