Swift技术疑问:`if let`块能否抛异常及JSON包装器报错与优化
嘿,让我来逐个解决你的问题!
if let代码块中抛出异常? 当然可以!但有个关键前提:你必须在支持抛出异常的上下文中使用。简单来说,要么包含这个if let的函数被标记为throws,要么把if let放在do-catch代码块里,这样抛出的异常能被正确捕获或传递。
举个实际的例子:
// 自定义错误类型 enum DataError: Error { case missingData case emptyContent } func processOptionalData(_ optionalData: String?) throws -> String { if let data = optionalData { if data.isEmpty { // 在if let代码块中抛出异常,因为函数标记了throws throw DataError.emptyContent } return data } else { throw DataError.missingData } } // 调用时需要用do-catch处理异常 do { let result = try processOptionalData("Hello Swift") print(result) } catch { print("Error occurred: \(error)") }
如果在非抛出上下文(比如普通的无throws标记的函数)里直接在if let中throw,编译器会报错。这时候你要么给函数加上throws,要么用do-catch把抛出的异常就地处理掉。
错误原因解析
你遇到的Invalid conversion from throwing function...错误,核心问题是:URLSession.dataTask的completion handler是**非抛出(non-throwing)**闭包——系统设计这个闭包时就不允许内部直接抛出异常,因为没有机制来捕获这些异常。所以你不能在这个闭包里写throw,得换一种方式传递错误(比如用Result类型)。
代码扁平化 + 错误修复方案
先推测你原来的代码大概有三层嵌套if:处理请求错误、校验数据存在、验证响应状态码。我们可以用guard let替代嵌套if,同时用Result<T, Error>类型让回调更安全、清晰。
改造后的代码如下:
// 先定义统一的网络错误类型 enum NetworkError: Error { case requestFailed(Error) case noDataReturned case invalidResponseType case invalidStatusCode(Int) case jsonDecodingFailed(Error) } func getJson<T: Decodable>(url: URL, completion: @escaping (Result<T, NetworkError>) -> Void) { URLSession.shared.dataTask(with: url) { data, response, error in // 1. 处理请求本身的错误 guard error == nil else { completion(.failure(.requestFailed(error!))) return } // 2. 确保有返回数据 guard let responseData = data else { completion(.failure(.noDataReturned)) return } // 3. 验证响应是合法的HTTP响应且状态码在200-299之间 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 completion(.failure(.invalidStatusCode(statusCode))) return } // 4. 解码JSON数据 do { let decodedModel = try JSONDecoder().decode(T.self, from: responseData) completion(.success(decodedModel)) } catch { completion(.failure(.jsonDecodingFailed(error))) } }.resume() }
这样用链式的guard let替代了三层嵌套if,代码结构更扁平,逻辑每一步都很清晰。而且用Result类型替代了原来的(T?, Error?)回调,避免了“两者都为nil”或“两者都不为nil”的不合理情况,类型安全性更高。
更现代的扁平化方案(Swift 5.5+)
如果你的项目支持Swift 5.5及以上,推荐使用async/await,代码会更简洁完全无嵌套:
func getJson<T: Decodable>(url: URL) async throws -> T { let (data, response) = try await URLSession.shared.data(from: url) // 验证响应状态码 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 throw NetworkError.invalidStatusCode(statusCode) } // 解码并返回 return try JSONDecoder().decode(T.self, from: data) }
这个版本直接用原生异步语法处理,不需要completion handler,异常通过throws直接传递,代码完全扁平,可读性拉满。
内容的提问来源于stack exchange,提问作者A T

