如何通过回调传递网络类错误至ViewController?解决类型不兼容编译错误
Hey there! Let's get this sorted out quickly. The error you're hitting happens because your completion closure's first parameter is defined as a non-optional UserDetail type, but you're trying to pass nil when an error occurs (since there's no valid user data to return in that case).
Here are two solid solutions to fix this:
1. Make the Completion's First Parameter Optional
The simplest fix is to update your network function's completion closure to accept an optional UserDetail? instead of the non-optional UserDetail. This lets you pass nil when there's an error, and a valid UserDetail instance when the request/parsing succeeds.
Before (Error-Prone Code):
func fetchUserDetail(completion: @escaping (UserDetail, Error?) -> Void) { // Your API request and parsing logic here if let parsingError = parsingError { completion(nil, parsingError) // ❌ Compiler error here return } completion(decodedUser, nil) }
After (Fixed Code):
func fetchUserDetail(completion: @escaping (UserDetail?, Error?) -> Void) { // Your API request and parsing logic here if let parsingError = parsingError { completion(nil, parsingError) // ✅ No more error return } completion(decodedUser, nil) }
In your ViewController, you'll then check if the user detail exists before using it:
networkManager.fetchUserDetail { user, error in if let error = error { // Handle the error (show alert, log, etc.) print("Failed to fetch user: \(error.localizedDescription)") return } guard let user = user else { // Edge case: no error but no user data (handle if needed) return } // Use the user data to update your UI self.updateUI(with: user) }
2. Use Swift's Result Type (Recommended)
For cleaner, more readable code (and to avoid ambiguous optional parameters), use Swift's built-in Result type. It's designed explicitly for success/failure scenarios like this.
Step 1: Update the Network Function
// Define a custom error enum for better error handling (optional but highly recommended) enum NetworkError: Error { case invalidURL case requestFailed(statusCode: Int) case parsingFailed } func fetchUserDetail(completion: @escaping (Result<UserDetail, NetworkError>) -> Void) { guard let url = URL(string: "your-api-url-here") else { completion(.failure(.invalidURL)) return } URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { // Wrap system error if needed, or return your custom error completion(.failure(.requestFailed(statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0))) return } guard let data = data else { completion(.failure(.requestFailed(statusCode: 0))) return } do { let decodedUser = try JSONDecoder().decode(UserDetail.self, from: data) completion(.success(decodedUser)) } catch { completion(.failure(.parsingFailed)) } }.resume() }
Step 2: Handle the Result in ViewController
networkManager.fetchUserDetail { result in DispatchQueue.main.async { // Always update UI on main thread! switch result { case .success(let user): self.updateUI(with: user) case .failure(let error): switch error { case .invalidURL: self.showAlert(title: "Error", message: "Invalid API URL") case .requestFailed(let statusCode): self.showAlert(title: "Request Failed", message: "Status code: \(statusCode)") case .parsingFailed: self.showAlert(title: "Error", message: "Failed to parse user data") } } } }
This approach makes your code more expressive—anyone reading it immediately knows that the closure will return either a successful UserDetail or a specific error type, no guesswork required.
内容的提问来源于stack exchange,提问作者Xcoder

