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

Swift中如何为API Manager的服务器登录创建正确的Completion Handler?

解决Swift API Manager登录方法中Completion Handler提前执行的问题

我来帮你搞定这个问题!你遇到的核心问题是把completion handler的调用时机搞错了——大概率是把它放在了网络请求的同步代码块里,导致还没等服务器返回响应就触发了。下面给你一步步展示正确的实现方式:


1. 重构你的API Manager登录方法

首先,我们要确保completion handler只在服务器返回响应后才被调用,也就是把它放在URLSession的异步回调内部。这里我用Result类型来统一处理成功/失败的情况,让逻辑更清晰:

import Foundation

// 先定义一个登录响应的模型(根据你的实际接口返回结构调整)
struct LoginResponse: Codable {
    let token: String
    let userId: Int
    // 其他你需要的字段
}

class APIManager {
    static let shared = APIManager() // 单例模式,方便全局调用
    
    func login(username: String, password: String, completion: @escaping (Result<LoginResponse, Error>) -> Void) {
        guard let url = URL(string: "https://your-server-url.com/login") else {
            completion(.failure(NSError(domain: "APIManager", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的登录URL"])))
            return
        }
        
        // 构建请求体
        let parameters: [String: Any] = [
            "username": username,
            "password": password
        ]
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
        
        // 发起网络请求
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // 先处理网络层面的错误
            if let error = error {
                completion(.failure(error))
                return
            }
            
            // 验证HTTP响应状态码
            guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
                let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
                let error = NSError(domain: "APIManager", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "登录失败,状态码:\(statusCode)"])
                completion(.failure(error))
                return
            }
            
            // 解析响应数据
            guard let data = data else {
                completion(.failure(NSError(domain: "APIManager", code: -2, userInfo: [NSLocalizedDescriptionKey: "服务器返回空数据"])))
                return
            }
            
            do {
                let loginResponse = try JSONDecoder().decode(LoginResponse.self, from: data)
                completion(.success(loginResponse))
            } catch {
                completion(.failure(error))
            }
        }
        
        task.resume()
        // 注意:这里绝对不能调用completion!否则会提前执行
    }
}

2. 在ViewController中调用并处理Segue

在你的登录ViewController里,调用这个方法时,要记得切换到主线程更新UI(因为网络请求的回调是在后台线程),然后在成功的分支里执行segue:

import UIKit

class LoginViewController: UIViewController {
    @IBOutlet weak var usernameTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    
    @IBAction func loginButtonTapped(_ sender: UIButton) {
        guard let username = usernameTextField.text, !username.isEmpty,
              let password = passwordTextField.text, !password.isEmpty else {
            // 提示用户输入账号密码
            let alert = UIAlertController(title: "提示", message: "请输入完整的账号和密码", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default))
            present(alert, animated: true)
            return
        }
        
        // 调用API登录
        APIManager.shared.login(username: username, password: password) { [weak self] result in
            DispatchQueue.main.async { // 必须切回主线程更新UI
                switch result {
                case .success(let response):
                    // 登录成功,保存token等信息(比如存在UserDefaults或者Keychain)
                    UserDefaults.standard.set(response.token, forKey: "authToken")
                    // 执行segue跳转到主界面
                    self?.performSegue(withIdentifier: "LoginToMain", sender: self)
                case .failure(let error):
                    // 登录失败,提示用户
                    let alert = UIAlertController(title: "登录失败", message: error.localizedDescription, preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "重试", style: .default))
                    self?.present(alert, animated: true)
                }
            }
        }
    }
}

为什么你的原来写法会提前执行?

大概率是你把completion()的调用放在了task.resume()之后的同步代码里,比如这样:

// ❌ 错误示例:completion在网络请求发起后立刻执行,根本没等响应
func login(username: String, password: String, completion: @escaping () -> Void) {
    // ...构建请求...
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        // ...处理响应,但没在这里调用completion...
    }
    task.resume()
    completion() // 这里会立刻执行!
}

正确的做法是必须把completion的调用放在dataTask的闭包内部,确保只有服务器返回响应、我们处理完状态码和数据之后,才触发completion。

内容的提问来源于stack exchange,提问作者Pouya Kahfi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:44:19