使用URLSession.dataTask时变量重置的原因及解决方法
Hey there, let's break down what's going on here and how to fix it. This isn't actually your variable getting "reset"—it's all about asynchronous execution timing.
Why this happens
URLSession.dataTask runs your network request on a background thread, which means it doesn't block the main thread while waiting for the response. When your code runs:
- You call
checkToken()inviewDidLoad() checkToken()callsfetchToken(), which starts the data task and immediately returns (the network request is still running in the background)- Your code moves right to
print(iftokened)—at this point, the network request hasn't finished yet, soiftokenedis still its initial valuefalse - Later, the network request completes, and the completion closure runs to set
iftokened = true—but by then, yourprintstatement has already executed, so you never see the updated value.
How to fix it
You need to wait until the asynchronous network request finishes before you act on the iftokened value. Here are a few solid approaches:
1. Handle the result directly in the completion closure (simplest solution)
Move your logic that depends on iftokened inside the completion handler, so it only runs after the network request completes:
override func viewDidLoad() { super.viewDidLoad() checkToken { [weak self] in // This code runs AFTER the network request finishes print(self?.iftokened ?? false) // Put any other logic that relies on iftokened here, like updating UI } } var iftokened = false // Update checkToken to take its own completion handler func checkToken(completion: @escaping () -> Void) { fetchToken { [weak self] (responseStatus) in self?.iftokened = (responseStatus == "200") // Call the completion to signal we're done completion() } } // Your existing fetchToken function stays mostly the same let session: URLSession = { let config = URLSessionConfiguration.default return URLSession(configuration: config) }() func fetchToken(completion: @escaping (String) -> Void) { guard let url = URL(string: "http://www.google.com") else { completion("error") return } let request = URLRequest(url: url) let task = session.dataTask(with: request) { (data, response, error) in // Note: In real code, you should check the actual HTTP status code from the response, not just hardcode "200" if let httpResponse = response as? HTTPURLResponse { completion(String(httpResponse.statusCode)) } else { completion("error") } } task.resume() }
2. Use Async/Await (iOS 15+, cleaner syntax)
If your app targets iOS 15 or later, you can use Swift's async/await to make asynchronous code look more synchronous, avoiding nested closures:
override func viewDidLoad() { super.viewDidLoad() // Run async code in a Task Task { await checkToken() print(iftokened) // Other logic here } } var iftokened = false func checkToken() async { let responseStatus = await fetchToken() iftokened = (responseStatus == "200") } // Convert fetchToken to an async function func fetchToken() async -> String { guard let url = URL(string: "http://www.google.com") else { return "error" } let request = URLRequest(url: url) do { let (_, response) = try await session.data(for: request) if let httpResponse = response as? HTTPURLResponse { return String(httpResponse.statusCode) } else { return "error" } } catch { return "error" } }
Important Note
Never block the main thread waiting for a network request (like using DispatchGroup.wait() on the main thread)—this will freeze your app's UI and lead to a bad user experience. Always use completion handlers or async/await to handle asynchronous work properly.
内容的提问来源于stack exchange,提问作者cRuiTe

