SwiftUI MVVM 将变量从 ViewModel 传递到 API 服务

Posted

技术标签:

【中文标题】SwiftUI MVVM 将变量从 ViewModel 传递到 API 服务【英文标题】:SwiftUI MVVM pass variable from ViewModel to API Service 【发布时间】:2021-01-24 09:21:06 【问题描述】:

我对 Swift 和 XCode 非常陌生,在学习了几个教程之后,我已经创建了一个带有 API 服务的 MVVM 架构,但是我无法将我的变量从 ViewModel 传递到我的 API 服务。我想传递 var 用户并从 LoginViewModel 传递到 APIEndpoint

登录视图模型

class LoginViewModel: ObservableObject, LoginService 
    // I want to send this 2 variables to APIEndpoint
    @Published var user = ""
    @Published var pass = ""
    
    var cancellables = Set<AnyCancellable>()
    
    init(apiSession: APIService = APISession()) 
        self.apiSession = apiSession
    
    
    func login() 
        let cancellable = self.login()
            .sink(receiveCompletion:  result in
                switch result 
                case .failure(let error):
                    print("Handle error: \(error)")
                case .finished:
                    break
                
                
            )  (detail) in
                print(detail)
        
        cancellables.insert(cancellable)
    

API端点

enum APIEndpoint 
    case userLogin
    case menuList


extension APIEndpoint: RequestBuilder 
    var urlRequest: URLRequest 
        switch self 
        case .userLogin:
             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"
            
            // I want to pass the variables here, how??
            let body: [String: Any] = ["user": $user, "pass": $pass]

            let rb = try! JSONSerialization.data(withJSONObject: body)
            request.httpBody = rb
            
            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
        
        
    

请求生成器

protocol RequestBuilder 
    var urlRequest: URLRequest get

登录服务

protocol LoginService 
    var apiSession: APIService get
    
    func login() -> AnyPublisher<LoginAPIResponse, APIError>


extension LoginService 
    
    func login() -> AnyPublisher<LoginAPIResponse, APIError> 
        return apiSession.request(with: APIEndpoint.userLogin)
            .eraseToAnyPublisher()
    

API服务

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

APISession

struct APISession: APIService 
    func request<T>(with builder: RequestBuilder) -> AnyPublisher<T, APIError> where T: Decodable 
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        
        return URLSession.shared
            .dataTaskPublisher(for: builder.urlRequest)
            .receive(on: DispatchQueue.main)
            .mapError  _ in .unknown 
            .flatMap  data, response -> AnyPublisher<T, APIError> in
                if let response = response as? HTTPURLResponse 
               
                    if (200...299).contains(response.statusCode) 
                        print(String(data: data, encoding: .utf8) ?? "")
                        return Just(data)
                            .decode(type: T.self, decoder: decoder)
                            .mapError _ in .decodingError
                            .eraseToAnyPublisher()
                     else 
                        return Fail(error: APIError.httpError(response.statusCode))
                            .eraseToAnyPublisher()
                    
                
                return Fail(error: APIError.unknown)
                        .eraseToAnyPublisher()
            
            .eraseToAnyPublisher()
    

提前谢谢大家。

【问题讨论】:

【参考方案1】:

您应该以这种形式更改您的枚举:

enum APIEndpoint 
    case userLogin(user: String, password: String)
    case menuList

更改您的 requestBuilder:

extension APIEndpoint: RequestBuilder 
    var urlRequest: URLRequest 
        switch self 
        case .userLogin(let user, let password):
             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]

            let rb = try! JSONSerialization.data(withJSONObject: body)
            request.httpBody = rb
            
            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
        
        
    

在此之后,在所有login 函数中,您应该将userpassword 作为参数传递

【讨论】:

感谢您的回答,但是我仍然无法从viewmodel中获取要传递给APIEndpoint的值,我需要在LoginViewModel和LoginService中做什么?对不起,我对此很陌生,已经尝试调整代码,但它只是不断弹出错误

以上是关于SwiftUI MVVM 将变量从 ViewModel 传递到 API 服务的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 中 MVVM 模式中多个切换的绑定变量数组

SwiftUI - MVVM之ViewModel

为扫雷游戏动态创建游戏板

如何为MVVM模式链接Retrofit和Repository / ViewModel?

SwiftUI 如何不在列表中复制 ViewModel

SwiftUI 在哪里加载 MVVM 中的数据