Swift泛型网络请求函数解码空响应数据失败的解决方法
问题
我编写了一个带泛型结果的网络请求函数fetch,代码如下:
private func fetch<Request, Response>(_ endpoint: Types.EndPoint, method: HTTPMethod = .get, body: Request? = nil, then callback: ((Result<Response, Types.Error>) -> Void)? = nil ) where Request: Encodable, Response: Decodable { //Network request creation... let dataTask = URLSession.shared .dataTask(with: urlRequest) { data, response, error in if let error = error { print("Fetch error: \(error)") callback?(.failure(.generic(reason: "Could not fetch data :\(error.localizedDescription)"))) } else { if let data = data { do { let result = try self.decoder.decode(Response.self, from: data) // **exception here** callback?(.success(result)) } catch { print("Decoding error: \(error)") callback?(.failure(.generic(reason: "Could not decode data :\(error.localizedDescription)"))) } } } } } ...... apiClient.fetch(.updateUser, API.Types.Request.User) { (result: Result<API.Types.Response.Empty, API.Types.Error>) in //handle result }
其中API.Types.Response.Empty是一个遵循Decodable协议的空结构体:
struct Empty: Decodable {}
调用该接口时,服务器成功返回状态码200,但响应内容为空(Content-Length为0),此时JSON解码抛出异常:
Unable to parse empty data." UserInfo={NSDebugDescription=Unable to parse empty data
请问该如何正确处理这种空响应数据的解码场景?
解决方案
方法一:给Empty结构体自定义解码逻辑
直接修改Empty结构体,让它兼容空数据的解码场景,不需要改动网络请求代码:
struct Empty: Decodable { init(from decoder: Decoder) throws { do { // 尝试解码空容器,空结构体本身没有编码键,不会报错 let _ = try decoder.container(keyedBy: CodingKeys.self) } catch { // 捕获空数据的解码错误,直接初始化实例 if let decodingError = error as? DecodingError, case .dataCorrupted(let context) = decodingError, context.debugDescription.contains("Unable to parse empty data") { return } // 其他错误正常抛出 throw error } } // 定义空的编码键枚举,避免默认解码逻辑报错 private enum CodingKeys: String, CodingKey {} }
这种方式把逻辑封装在Empty结构体内部,其他地方调用时不需要额外判断。
方法二:在fetch函数中提前处理空数据
在解码前先检查数据是否为空,如果是空数据且目标类型是Empty,直接返回成功实例:
private func fetch<Request, Response>(_ endpoint: Types.EndPoint, method: HTTPMethod = .get, body: Request? = nil, then callback: ((Result<Response, Types.Error>) -> Void)? = nil ) where Request: Encodable, Response: Decodable { //Network request creation... let dataTask = URLSession.shared .dataTask(with: urlRequest) { data, response, error in if let error = error { print("Fetch error: \(error)") callback?(.failure(.generic(reason: "Could not fetch data :\(error.localizedDescription)"))) } else { if let data = data { // 空数据且目标类型是Empty时直接返回成功 if data.isEmpty, Response.self is Empty.Type { callback?(.success(Empty() as! Response)) return } do { let result = try self.decoder.decode(Response.self, from: data) callback?(.success(result)) } catch { print("Decoding error: \(error)") callback?(.failure(.generic(reason: "Could not decode data :\(error.localizedDescription)"))) } } else { // 数据为nil的情况,同样判断是否是Empty类型 if Response.self is Empty.Type { callback?(.success(Empty() as! Response)) } else { callback?(.failure(.generic(reason: "Empty data received but expected non-empty response"))) } } } } }
这种方式把判断逻辑集中在网络请求层,适合不想修改Empty结构体的场景。
方法三:扩展JSONDecoder支持空数据解码
给JSONDecoder添加一个扩展方法,统一处理空数据的解码逻辑:
extension JSONDecoder { func decodeHandlingEmpty<T: Decodable>(_ type: T.Type, from data: Data) throws -> T { guard !data.isEmpty else { // 如果是Empty类型,返回实例;否则抛出自定义错误 guard let emptyType = T.self as? Empty.Type else { throw DecodingError.dataCorrupted( DecodingError.Context( codingPath: [], debugDescription: "Empty data received but expected non-empty response" ) ) } return emptyType.init() as! T } return try decode(type, from: data) } }
然后在fetch函数中替换原来的解码代码:
do { let result = try self.decoder.decodeHandlingEmpty(Response.self, from: data) callback?(.success(result)) } catch { print("Decoding error: \(error)") callback?(.failure(.generic(reason: "Could not decode data :\(error.localizedDescription)"))) }
这种方式的好处是可以把空数据处理逻辑复用在其他需要的地方。
内容的提问来源于stack exchange,提问作者Sachinthana Aluvihare
相关产品推荐
相关产品推荐

