You need to enable JavaScript to run this app. SwiftUI MVVM中如何实现LoginViewModel向APIEndpoint传递登录参数
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI MVVM中如何实现LoginViewModel向APIEndpoint传递登录参数

实现方法

核心问题在于目前APIEndpoint是不带动态参数的纯枚举值,无法绑定登录时的临时输入值。只需要顺着现有MVVM分层的调用链路,把需要的参数逐层传递到endpoint构造环节即可,不需要改动现有架构的核心结构。

第一步:给APIEndpoint增加关联值存储动态参数

把原来的纯case枚举改成带关联值的形式,需要传参的userLogin case直接绑定用户输入的账号、密码:

enum APIEndpoint {
    // 为需要动态传参的case添加关联值,存储登录所需的user、pass
    case userLogin(user: String, pass: String)
    case menuList
}

同步修改RequestBuilder扩展里的urlRequest计算属性,在匹配case时取出关联值,拼接到请求体中:

extension APIEndpoint: RequestBuilder {
    var urlRequest: URLRequest {
        switch self {
        case .userLogin(let user, let pass): // 从枚举关联值取出参数
             guard let url = URL(string: "https://jcouserapidev.oxxo.co.id/home/app")
                else {preconditionFailure("Invalid URL format")}
            var request = URLRequest(url: url)
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.setValue(Constants.API_TOKEN, forHTTPHeaderField: "Authorization")
            request.httpMethod = "POST"
            
            // 直接使用取出的关联值构造请求体
            let body: [String: Any] = ["user": user, "pass": pass]
            do {
                request.httpBody = try JSONSerialization.data(withJSONObject: body)
            } catch {
                preconditionFailure("请求体序列化失败: \(error)")
            }
            return request
            
        case .menuList:
            guard let url = URL(string: "https://jcouserapidev.oxxo.co.id/home/app")
                else {preconditionFailure("Invalid URL format")}
            var request = URLRequest(url: url)
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.setValue(Constants.API_TOKEN, forHTTPHeaderField: "Authorization")
            request.httpMethod = "POST"
            return request
        }
    }
}

这里顺手修正了原代码中强行force try的隐患,JSON序列化失败时会抛出明确错误提示,避免无意义崩溃

第二步:修改LoginService协议,让login方法接收登录参数

原来的LoginService扩展里的login方法是硬编码使用空参数的APIEndpoint.userLogin,现在调整为接收两个参数,构造带正确参数的endpoint实例:

protocol LoginService {
    var apiSession: APIService {get}
    // 协议方法补充需要传入的user、pass参数
    func login(user: String, pass: String) -> AnyPublisher<LoginAPIResponse, APIError>
}

extension LoginService {
    func login(user: String, pass: String) -> AnyPublisher<LoginAPIResponse, APIError> {
        // 构造携带正确参数的endpoint实例,传递给apiSession发起请求
        let endpoint = APIEndpoint.userLogin(user: user, pass: pass)
        return apiSession.request(with: endpoint)
            .eraseToAnyPublisher()
    }
}

第三步:修改LoginViewModel的调用逻辑,传递自身持有的参数

原来的login()方法是无参调用,现在调用服务层方法时,把当前ViewModel持有的@Published属性传进去即可:

class LoginViewModel: ObservableObject, LoginService {
    @Published var user = ""
    @Published var pass = ""
    
    var cancellables = Set<AnyCancellable>()
    var apiSession: APIService
    
    init(apiSession: APIService = APISession()) {
        self.apiSession = apiSession
    }
    
    func login() {
        // 调用服务层login方法时,传入当前ViewModel持有的user、pass
        let cancellable = self.login(user: self.user, pass: self.pass)
            .sink(receiveCompletion: { result in
                switch result {
                case .failure(let error):
                    print("Handle error: \(error)")
                case .finished:
                    break
                }
            }) { (detail) in
                print(detail)
        }
        cancellables.insert(cancellable)
    }
}

顺带修正原代码中的编译错误

你原代码里的APIService协议声明的request方法多了一个无实际作用的test: String参数,但是APISession的实现里并没有对应参数,会导致编译报错,直接删掉这个多余参数即可,和APISession的实现保持一致:

protocol APIService {
    func request<T: Decodable>(with builder: RequestBuilder) -> AnyPublisher<T, APIError>
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.07 15:54:05