Swift 5中使用Decodable链式发起JSON请求的最优方案问询
Hey there! Let's tackle your two main issues: structuring chained API requests properly and fixing that second JSON parsing error.
一、请求逻辑的结构选择:拆分复用优于重复代码
First off, never duplicate network request logic—it's messy, hard to maintain, and increases the chance of bugs. Instead of copying the URLSession code into a separate function or cramming everything into one block, you should:
- Create a reusable generic network request function that handles decoding any Codable model.
- Call this function sequentially: first for the initial search, then for each retrieved link.
This keeps your code clean, testable, and easy to debug.
二、修复第二个请求的解析错误(以及隐藏的逻辑问题)
Looking at your current code, there are a few key issues causing the parsing error and unexpected behavior:
1. Incorrect link access in mainParse()
You're always using collectLink[0] to build the URL, but if your first request returns multiple links, you'll repeatedly request the same first link instead of each unique one. Worse, if the array is empty when mainParse() runs (race condition from async tasks), you'll crash with an index out of bounds error.
Fix: Pass the retrieved validLink directly to your second request function instead of relying on an array index.
2. Mismatched Codable model vs. JSON response
The "Parse Error" in the second request almost always means your JSONResponse struct doesn't match the actual JSON structure being returned. To debug this:
- Add a print statement inside the data task to log the raw JSON string:
if let rawJson = String(data: data, encoding: .utf8) { print("Raw second response JSON: \(rawJson)") } - Compare this raw JSON to your
JSONResponseand associated structs (like the type oflocationselements). Check for:- Case mismatches (e.g., JSON uses
ridbut your model hasRid) - Missing optional markers (e.g., a JSON field that can be
nullbut your model uses a non-optional type) - Wrong property types (e.g., JSON returns a string but your model expects an integer)
- Case mismatches (e.g., JSON uses
3. Unnecessary array storage (unless you need it later)
If you don't need to keep all the links for future use, you don't even need the collectLink array—just pass each link directly to the second request as you parse it.
优化后的代码示例
Here's a refactored version of your code that fixes these issues:
Step 1: Create a reusable generic network request function
func fetchAndDecode<T: Codable>(urlString: String, completion: @escaping (Result<T, Error>) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(NSError(domain: "Invalid URL", code: -1, userInfo: nil))) return } let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(NSError(domain: "No data received", code: -2, userInfo: nil))) return } do { let decoded = try JSONDecoder().decode(T.self, from: data) completion(.success(decoded)) } catch { completion(.failure(error)) } } task.resume() }
Step 2: Update the search bar handler
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { searchBar.resignFirstResponder() guard let searchText = searchController.searchBar.text, !searchText.isEmpty else { return } // Encode the search text to avoid URL issues (critical for special characters!) guard let encodedSearchText = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return } let initialUrl = "http://djp-dev/api/item?q=\(encodedSearchText)&dev=1" fetchAndDecode(urlString: initialUrl) { [weak self] (result: Result<Response, Error>) in guard let self = self else { return } switch result { case .success(let response): for result in response.results { if let validLink = result.link { // Request each link immediately as we parse it self.fetchDetails(for: validLink) } } case .failure(let error): print("Initial request error: \(error.localizedDescription)") } } }
Step 3: Create a dedicated function for the second request
func fetchDetails(for link: String) { let detailUrl = "http://djp-dev\(link)?dev=1" fetchAndDecode(urlString: detailUrl) { [weak self] (result: Result<JSONResponse, Error>) in guard let self = self else { return } switch result { case .success(let response): // Process your locations data here for location in response.locations { print(""" Manufacture = \(location.rid) Description = \(location.description ?? "No description") """) } // Update UI on main thread DispatchQueue.main.async { self.tableView.reloadData() } case .failure(let error): print("Detail request error: \(error.localizedDescription)") // Optional: Print the decoding error details for debugging if let decodingError = error as? DecodingError { print("Decoding error details: \(decodingError)") } } } }
Key improvements in this code:
- Reusability: The
fetchAndDecodefunction handles all network and decoding logic, so you don't repeat code. - Safety: Uses
[weak self]to avoid retain cycles, safely unwraps optional values, and encodes the search text to prevent URL invalidity. - Clarity: Separates concerns into dedicated functions for search handling and detail fetching.
- Debuggability: Provides specific error messages and logs decoding errors to help you fix the JSON model mismatch.
内容的提问来源于stack exchange,提问作者Dewan

