URLSession网络请求回调闭包接收数据后未执行问题
问题根因与修复方案
核心导致闭包不执行的原因
- 系统ATS安全策略拦截:你使用的是
http非加密协议请求,iOS默认的App Transport Security策略会直接拦截所有未声明例外的HTTP请求,拦截发生在系统层面,不会触发URLSession的任何回调,这是闭包完全不执行的最常见原因。 - 代码逻辑存在异步时序错误:
URLSession.dataTask是异步执行的,调用resume()后请求才会被调度发起,此时立刻同步判断登录状态、返回Bool值,请求还未收到响应,逻辑必然不成立。另外代码中判断的self.loggedInUser属性根本没有在ViewModel中声明,属于无效代码。 - 额外逻辑错误:给JSON解码器设置了
.base64的data解码策略,该策略仅用于处理接口返回的base64编码二进制数据,普通JSON响应不需要该配置,会导致后续解码失败;且网络回调默认在子线程执行,直接更新UI绑定的状态属性没有切换主线程,会引发UI异常。
分步修复
配置ATS HTTP请求例外
打开项目的Info.plist文件,添加如下配置:- 根节点添加
App Transport Security Settings(Dictionary类型) - 在该字典下可直接添加
Allow Arbitrary Loads设为YES开放所有HTTP请求,或添加Exception Domains(Dictionary类型),在其内部添加www.venndor.co.uk节点,将该节点下的NSExceptionAllowsInsecureHTTPLoads设为YES,仅对接口域名放开HTTP限制。
- 根节点添加
修正ViewModel网络请求逻辑
废弃同步返回Bool的写法,使用异步回调处理请求结果,修正解码配置,所有UI状态更新切换到主线程,参考修复代码:
import Foundation import SwiftUI import CoreLocation struct DbCustomer: Codable, Identifiable { var id: String var user_name: String var pass_word: String var fullname: String } class LunchboxViewModel: ObservableObject { @AppStorage("loggedIn") var loggedIn: Bool = false @AppStorage("username") var username: String = "" @AppStorage("password") var password: String = "" @AppStorage("fullname") var fullname: String = "" @AppStorage("userid") var userId: String = "" // 改为逃逸闭包异步回调结果,不要同步返回 func loginInUser(username: String, completion: @escaping (Bool) -> Void) { var urlComponents = URLComponents() urlComponents.scheme = "http" urlComponents.host = "www.venndor.co.uk" urlComponents.path = "/logindo.php" urlComponents.queryItems = [ URLQueryItem(name: "username", value: username) ] guard let url = urlComponents.url else { completion(false) return } var request = URLRequest(url: url) request.httpMethod = "POST" URLSession.shared.dataTask(with: request) { [weak self] data, response, error in // 先判断请求错误 if let error = error { print("请求错误: \(error.localizedDescription)") DispatchQueue.main.async { completion(false) } return } // 校验响应状态 guard let httpResp = response as? HTTPURLResponse, httpResp.statusCode == 200, let data = data else { DispatchQueue.main.async { completion(false) } return } do{ let decoder = JSONDecoder() // 删除错误的base64解码策略,普通JSON不需要 let decodedData = try decoder.decode([DbCustomer].self, from: data) guard let user = decodedData.first else { DispatchQueue.main.async { completion(false) } return } // UI/状态更新必须切主线程 DispatchQueue.main.async { self?.username = user.user_name self?.password = user.pass_word self?.userId = user.id self?.fullname = user.fullname self?.loggedIn = true completion(true) } }catch let error{ print("解码错误: \(error)") DispatchQueue.main.async { completion(false) } } }.resume() } }
- 修正页面调用逻辑
之前在handleButtonPress里直接同步判断方法返回值的写法完全不适用异步请求,改为等待回调结果后再执行页面跳转:
func handleButtonPress() { if signUpProgress == 1 { // 等待异步请求回调后再处理跳转 viewModel.loginInUser(username: username) { [weak self] isSuccess in guard let self = self else { return } if isSuccess { self.loginSignUpOption = "logIn" } else { // 可在这里加登录失败提示 print("登录失败") } } // 异步请求需要等待回调,直接return终止后续流程 return } if companyname == "" && signUpProgress > 2 { let newUser = DbCustomer(id: "0", user_name: username, pass_word: "", fullname: fullname) viewModel.signUpUser(user: newUser) loginSignUpOption = "logIn" return } if signUpProgress == 5 { let newUser = DbCustomer(id: "0", user_name: username, pass_word: "", fullname: fullname) let newCompany = DbVendor(id: "0", company_name: companyname, description: companydescription, customer_id: "0") viewModel.signUpUserWithCompany(user: newUser, company: newCompany) loginSignUpOption = "logIn" return } else { withAnimation(.spring()) { signUpProgress += 1 } } }
补充:如果配置完ATS后闭包还是不执行,可以先把请求地址临时改成可用的HTTPS测试地址验证回调逻辑,确认是ATS拦截问题后再仔细核对Info.plist的配置格式,节点类型或拼写错误会导致配置完全不生效。
内容的提问来源于stack exchange,提问作者HillInHarwich
相关产品推荐
相关产品推荐

