You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在执行Segue前校验服务器响应值?异步请求问题解决

问题分析与解决方案

首先,咱们来拆解一下你遇到的核心问题:网络请求是异步执行的,但你在触发请求后立刻就判断error并执行Segue,这时候服务器的响应还没回来,itemsUploaded回调根本没机会修改error的值。哪怕self.delegate是在主队列执行,那也是在doneButtonTouched执行完之后的事了——Segue早已经跑起来了。

为什么主队列的delegate没起作用?

你说的没错,itemsUploaded确实是在主队列执行的,但执行顺序搞反了:

  1. 点击按钮,doneButtonTouched同步执行,调用uploadSpecialistWithId后,立刻检查error(此时error还是初始的false),直接触发Segue。
  2. 之后网络请求才完成,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:27:03