Swift 4.1中POST请求获取Token时方法返回值异常问题
问题分析与解决方案:异步网络请求导致返回值异常
嘿,我一眼就看出问题所在了——你遇到了异步代码的经典陷阱!
问题根源
URLSession.shared.dataTask(with:) 是一个异步执行的网络请求。当你调用 task.resume() 后,这个请求会在后台线程默默执行,而主线程不会停下来等它完成,会直接继续往下走,执行 return funcResult。这时候 funcResult 还是你初始化的 false,等网络请求成功拿到 Token 并把 funcResult 改成 true 的时候,方法早就已经返回了,所以你永远拿到的是 false。
调试时看到代码从 let task = 直接跳到 task.resume() 然后返回,就是因为主线程不会等待异步任务完成,直接执行后续代码了。
解决方案:用闭包回调代替同步返回值
异步操作不能用同步返回值的方式来获取结果,最常用的解决办法是使用闭包回调。我们修改一下你的代码:
首先重构 getTokenWithPostMethod 方法,把返回值换成闭包:
func getTokenWithPostMethod(completion: @escaping (Bool, String?) -> Void) { guard let url = URL(string: "https://apiABCDEFG.com/token") else { completion(false, nil) return } var request = URLRequest(url: url) request.httpMethod = "POST" let postString = "grant_type=password&username=ABCDEF&password=ABCDEF" request.httpBody = postString.data(using: .utf8) request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") let task = URLSession.shared.dataTask(with: request) { data, response, error in // 处理网络错误 guard let data = data, error == nil else { print("error=\(String(describing: error))") completion(false, nil) return } // 检查HTTP状态码 if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { print("statusCode should be 200, but is \(httpStatus.statusCode)") print("response = \(String(describing: response))") completion(false, nil) return } // 解析JSON do { if let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] { let token = jsonObject["access_token"] as? String let tokenType = jsonObject["token_type"] as? String print(token ?? "") print("\n") print(tokenType ?? "") // 请求成功,通过闭包返回结果 completion(true, token) } else { completion(false, nil) } } catch let error as NSError { print(error) completion(false, nil) } } task.resume() }
然后修改按钮点击的处理函数,通过闭包接收结果并更新UI:
@IBAction func btnHitWebServiceClick(_ sender: Any) { getTokenWithPostMethod { [weak self] success, token in // 注意:网络回调在后台线程,更新UI必须切回主线程 DispatchQueue.main.async { guard let self = self else { return } if success, let token = token { self.lblResult.text = "yes, we get token: " + token } else { self.lblResult.text = "there is an error, please try again later" } } } }
额外提示
- 避免全局变量:你原来用的
g_token和g_token_type尽量不要用,通过闭包传递数据更安全,也符合Swift的编码规范。 - 防止循环引用:用
[weak self]捕获self,避免ViewController因为网络任务的闭包而无法被释放,造成内存泄漏。 - UI更新必须在主线程:所有UI操作都要放在主线程执行,所以我们用了
DispatchQueue.main.async包裹UI更新代码。
这样修改后,当网络请求完成时,闭包会被触发,你就能正确拿到Token并更新标签内容了。
内容的提问来源于stack exchange,提问作者mannyCalavera
相关产品推荐
相关产品推荐

