iOS Swift应用网络请求异步问题:为何先输出‘I'm here’再返回JSON?
Hey there! Let's break down exactly why your output is out of order, and walk through the best ways to fix it in Swift.
The Root Cause: Asynchronous Network Calls
Nearly all network requests in iOS (like using URLSession) run asynchronously. That means when you call dataTask.resume(), the system kicks off the network work on a background thread—it doesn't pause your main thread to wait for the response to come back.
Your code keeps executing line-by-line on the main thread, so the print("I'm here before JSON") runs immediately, while the network request chugs along in the background. Only once the request finishes (success or failure) does the closure/handler you passed to dataTask get executed.
Here's what your code probably looks like right now, to illustrate:
func getWebsiteContent() { let url = URL(string: "https://your-target-site.com")! let task = URLSession.shared.dataTask(with: url) { data, response, error in // This code runs LATER, when the network call finishes if let data = data { print("Parsed JSON successfully!") } } task.resume() // This code runs RIGHT AWAY, before the network call finishes print("I'm here before JSON") }
How to Fix It: Handle Asynchronous Code Properly
You need to ensure any code that depends on the network response only runs after the response is received. There are two modern, recommended ways to do this in Swift:
1. Use Completion Handlers (Older but Still Widely Used)
Wrap your network logic in a function that takes a completion closure—this lets you pass in the code you want to run once the request finishes.
// Define the fetch function with a completion handler func fetchWebsiteContent(completion: @escaping (Result<Data, Error>) -> Void) { guard let url = URL(string: "https://your-target-site.com") else { completion(.failure(NSError(domain: "InvalidURL", code: 0, userInfo: nil))) return } let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(error)) return } guard let responseData = data else { completion(.failure(NSError(domain: "NoData", code: 0, userInfo: nil))) return } completion(.success(responseData)) } task.resume() } // Call the function and handle the result fetchWebsiteContent { result in switch result { case .success(let data): // Parse your JSON or process the data HERE print("Got JSON response!") // If you need to update UI, switch back to the main thread: DispatchQueue.main.async { // Update labels, tables, etc. } case .failure(let error): print("Request failed: \(error.localizedDescription)") } } // Any code here still runs before the network call finishes, and that's okay!
2. Use Async/Await (Swift 5.5+, iOS 15+)
This is the cleaner, more modern approach that eliminates "callback hell" by letting you write asynchronous code that reads like synchronous code.
// Define an async throwing function for the network call func fetchWebsiteContent() async throws -> Data { guard let url = URL(string: "https://your-target-site.com") else { throw NSError(domain: "InvalidURL", code: 0, userInfo: nil) } // `data(from:)` is an async method that waits for the response let (data, _) = try await URLSession.shared.data(from: url) return data } // Call the function from an async context (like a Task) Task { do { let data = try await fetchWebsiteContent() // Parse JSON or process data HERE print("Got JSON response!") // Update UI safely using MainActor await MainActor.run { // Update your UI elements here } } catch { print("Request failed: \(error.localizedDescription)") } }
Key Takeaway
Never assume asynchronous operations (like network calls) will finish before the next line of code runs. Always use completion handlers or async/await to chain your logic so that code dependent on the response runs only after the response is available.
内容的提问来源于stack exchange,提问作者JackDevelop

