如何在执行Segue前校验服务器响应值?异步请求问题解决
问题分析与解决方案
首先,咱们来拆解一下你遇到的核心问题:网络请求是异步执行的,但你在触发请求后立刻就判断error并执行Segue,这时候服务器的响应还没回来,itemsUploaded回调根本没机会修改error的值。哪怕self.delegate是在主队列执行,那也是在doneButtonTouched执行完之后的事了——Segue早已经跑起来了。
为什么主队列的delegate没起作用?
你说的没错,itemsUploaded确实是在主队列执行的,但执行顺序搞反了:
- 点击按钮,
doneButtonTouched同步执行,调用uploadSpecialistWithId后,立刻检查error(此时error还是初始的false),直接触发Segue。 - 之后网络请求才完成,
itemsUploaded在主队列执行,这时候修改error或者显示提示已经晚了,Segue早就跳过去了。
解决步骤:把Segue的触发逻辑移到回调里
核心思路就是等服务器响应处理完成后,再决定是否执行Segue,而不是在触发请求后立刻判断。
1. 修改ViewController的doneButtonTouched方法
去掉里面的Segue执行逻辑,只负责触发网络请求,还可以加个加载提示防止用户重复点击:
@IBAction func doneButtonTouched(_ sender: Any) { // 可选:添加加载状态提示 activityIndicator.startAnimating() // 只触发网络请求,不处理Segue uploadModel.uploadSpecialistWithId(login: loginTextField.text!, password: passwordTextField.text!) }
2. 在itemsUploaded回调里处理结果并触发Segue
只有当成功获取到Specialist对象时,才执行Segue;如果是错误码,就显示提示:
func itemsUploaded(items: NSArray) { // 停止加载状态 activityIndicator.stopAnimating() if let errorCode = items[0] as? Int { if errorCode == 11000 { warningLabel.text = "This login is already used" } } else if let specialist = items[0] as? Specialist { self.specialistId = specialist.specialistId! print("Value after itemsUploaded --> \(self.specialistId)") // 只有成功时才触发Segue performSegue(withIdentifier: "regToRegCompleteSegue", sender: self) } }
进阶优化:用Completion Handler替代Delegate(可选)
如果你觉得Delegate模式有点繁琐,可以把UploadModel的方法改成带Completion Handler的形式,代码会更简洁直观:
修改UploadModel.swift的方法
func uploadSpecialistWithId(login: String, password: String, completion: @escaping (Result<Specialist, Int>) -> Void) { let requestUrl = URL(string: "http://localhost:3000/api/register/specialist")! var request = URLRequest(url: requestUrl) request.httpMethod = "POST" let postParams = "login=\(login)&password=\(password)" print(postParams) request.httpBody = postParams.data(using: .utf8) let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { print("error -> \(error.localizedDescription)") DispatchQueue.main.async { completion(.failure(-1)) // 自定义通用错误码 } return } guard let data = data else { DispatchQueue.main.async { completion(.failure(-2)) } return } do { let jsonResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary ?? [:] if let login = jsonResult["login"] as? String, let specialistId = jsonResult["_id"] as? String { let specialist = Specialist() specialist.login = login specialist.specialistId = specialistId print("Added \(specialist.itemDescribtion())") DispatchQueue.main.async { completion(.success(specialist)) } } else { let errorCode = jsonResult["code"] as? Int ?? -3 print("Error with code -> \(errorCode)") DispatchQueue.main.async { completion(.failure(errorCode)) } } } catch let error as NSError { print("error -> \(error.localizedDescription)") DispatchQueue.main.async { completion(.failure(-4)) } } } task.resume() }
然后在ViewController里调用
@IBAction func doneButtonTouched(_ sender: Any) { activityIndicator.startAnimating() uploadModel.uploadSpecialistWithId(login: loginTextField.text!, password: passwordTextField.text!) { [weak self] result in guard let self = self else { return } self.activityIndicator.stopAnimating() switch result { case .success(let specialist): self.specialistId = specialist.specialistId! self.performSegue(withIdentifier: "regToRegCompleteSegue", sender: self) case .failure(let errorCode): if errorCode == 11000 { self.warningLabel.text = "This login is already used" } else { self.warningLabel.text = "Registration failed with error code \(errorCode)" } } } }
这种方式避免了NSArray和Delegate的额外处理,逻辑更清晰,也更符合Swift的现代编程风格。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

