Alamofire + Combine + MVVM request example

孤街浪徒 提交于 2021-01-28 06:44:52

问题


in my view controller I have a property items to which I have a subscription and rendering my view.

For this view controller I have a view model where I have load method like:

    @Published private(set) var items: [Item] = []

    func load(params: [String: Any] = [:]) {
        self.isLoading = true
        
        self.subscription = WebRepo().index(params: params).sink(receiveCompletion: { [weak self] (completion) in
            switch completion {
            case .failure(let error):
                print("Error is: \(error.localizedDescription)")
            default: break
            }
            self?.isLoading = false
        }, receiveValue: { [weak self] (response) in
            self?.items = response.data
        })
    }

and my WebRepo looks like:

final class WebRepo {
    func index(params: [String: Any]) -> AnyPublisher<MyResponse<[Item]>, Error> {
        let url = URL(...)
        return AF.request(url, method: .get, parameters: params)
        .publishDecodable(type: MyResponse<[Item]>.self)
        .tryCompactMap { (response) -> MyResponse<[Item]>? in
            if let error = response.error { throw error }
            return response.value
        }
        .eraseToAnyPublisher()
    }
}

My user can load multiple times and as you can see It will subscribe each time when load method is called, which I think should not be like this.

I tried to introduce property for my view model:

private var indexResponse: AnyPublisher<MyResponse<[Item]>, Error>?

//And my load becomes

func load(params: [String: Any] = [:]) {
   self.isLoading = true
   self.indexResponse = WebRepo().index(params: params)
}

But in this case I can't make initial binding since initial value is nil hence it won't subscribe.

Another question is about handling error from load, do I need to have property for error inside view model or is there way I can rethrow an error for $items?


回答1:


You can setup a single subscription in init with a PassthroughSubject as a trigger, and .send inside load:

private var cancellables: Set<AnyCancellable> = []
private let loadTrigger = PassthroughSubject<[String: Any], Never>()

init(...) {
   loadTrigger
     //.debounce(...) // possibly debounce load requests
     .flatMap { params in
         WebRepo().index(params: params)
     }
     .sink(
       receiveCompletion: {/* ... */},
       receiveValue: {/* ... */})
     .store(in: &cancellables)
}

func load(params: [String: Any] = [:]) {
    loadTrigger.send(params)
}

I can't tell you how to handle the error, since it's very case specific and probably subjective, e.g. should an error terminate the subscription? do you plan to handle possible multiple errors?


If you want to never fail, but still handle an error as a side effect, you can return Result, which would capture both value or error. This way, the pipeline could have a failure type of Never, so it would never terminate due to error. Then you'd unwrap the result inside the .sink:

loadTrigger
   .flatMap { params -> AnyPublisher<Result<MyResponse<[Item]>, Error>, Never> in
       WebRepo().index(params: params)
          .map { .success($0) }
          .catch { Just(.failure($0)) }
          .eraseToAnyPublisher()
   }
   .sink { [weak self] result in
       switch result {
       case success(let response): 
           self?.items = response.data
       case failure(let err):
           // do something with the error
       }
   }


来源:https://stackoverflow.com/questions/64697969/alamofire-combine-mvvm-request-example

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!