如何在Swift网络操作工作流中添加异常抛出的错误处理
回调式异步接口的错误处理方案
核心问题说明
你当前使用的是闭包回调式的异步接口,这类接口无法直接使用Swift同步throw语法抛出错误——异步代码的执行栈和调用方已经分离,throw的错误无法传递到外层的异步上下文。
你现有代码的核心问题是:所有校验/编码失败的分支都直接return,没有主动调用completion闭包,导致调用方永远收不到错误回调,甚至出现回调卡死的情况。
具体修改方案
1. 细化错误枚举,明确错误类型
先完善你定义的ContentServiceError,对应不同的失败场景,方便调用方精准处理:
enum ContentServiceError: Error, LocalizedError { case idIsEmpty case urlInvalid case encodingError(underlying: Error) // 可选:补充错误描述,便于调试 var errorDescription: String? { switch self { case .idIsEmpty: return "传入的内容ID为空" case .urlInvalid: return "请求URL构造失败" case .encodingError(let underlying): return "内容编码失败:\(underlying.localizedDescription)" } } }
2. 改造编码函数,改用同步throws抛出错误
把原来返回可选Data?的contentData函数改为同步throws版本,移除返回可选的隐式错误标记,同时修正原函数参数标签和调用处不匹配的问题:
private func contentData(from content: Content) throws -> Data { let encoder = JSONEncoder() // 编码失败直接抛出底层错误,由上层捕获 return try encoder.encode(content) }
3. 改造主接口,所有异常分支必须调用completion
把原来嵌套的guard拆成独立校验分支,每个失败分支都主动调用completion传入对应错误,成功分支正常发起网络请求:
func postContent( id: String?, content: String, completion: @escaping(Error?) -> () ) { // 校验ID合法性 guard let validID = id else { completion(ContentServiceError.idIsEmpty) return } // 校验URL合法性 guard let url = URL(string: "\(baseURL)\(contentEndPoint)\(validID)") else { completion(ContentServiceError.urlInvalid) return } // 捕获编码阶段抛出的错误 let contentData: Data do { contentData = try contentData(from: Content(id: validID, content: content)) } catch { completion(ContentServiceError.encodingError(underlying: error)) return } // 所有校验通过后发起网络请求 let request = Request(headers: nil, httpMethod: .post, httpBody: contentData, parameters: nil, url: url) let networkHandler = NetworkHandler(urlSession: URLSession(configuration: URLSessionConfiguration.ephemeral)) networkHandler.dataRequest(request, successHandler: { data in completion(nil) }, failureHandler: { error in completion(error) }) }
关于throw的使用说明
你代码里标注的两处「// how to add throw here」,在当前回调式异步接口里无法直接使用
throw。throw仅适用于同步函数的错误抛出,调用方需要在同一个执行栈内用do-catch捕获。
如果需要使用throw语法,可以把现有回调接口封装为async/await的异步函数,示例如下:
func postContent(id: String?, content: String) async throws { try await withCheckedThrowingContinuation { continuation in postContent(id: id, content: content) { error in if let error = error { continuation.resume(throwing: error) } else { continuation.resume() } } } }
封装后调用方就可以直接用do-catch处理错误:
DispatchQueue.global(qos: .background).async { do { try await postContent(id: id, content: content) await MainActor.run { /* 成功回调,切回主线程处理UI */ } } catch { await MainActor.run { /* 错误处理,切回主线程提示用户 */ } } }
内容的提问来源于stack exchange,提问作者konya
相关产品推荐
相关产品推荐

