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
相关产品推荐
相关产品推荐

