Alamofire + Combine + MVVM 请求示例

Alamofire + Combine + MVVM request example

在我的视图控制器中,我有一个 属性 items,我订阅并呈现了我的视图。

对于这个视图控制器,我有一个视图模型,其中我有如下加载方法:

    @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
        })
    }

我的 WebRepo 看起来像:

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()
    }
}

我的用户可以加载多次,如你所见,每次调用load方法时都会订阅,我认为不应该这样。

我尝试为我的视图模型引入 属性:

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)
}

但在这种情况下我无法进行初始绑定,因为初始值为 nil 因此它不会订阅。

另一个问题是关于处理加载错误,我是否需要在视图模型中为 error 使用 属性,或者我是否可以重新抛出 $items 的错误?

您可以在 init 中设置单个订阅,将 PassthroughSubject 作为触发器,并在 load:

中设置 .send
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)
}

我无法告诉您如何处理该错误,因为它非常具体且可能是主观的,例如错误应该终止订阅吗?您打算处理可能出现的多个错误吗?


如果您希望永不失败,但仍将错误作为副作用处理,您可以 return Result,这将同时捕获值或错误。这样,管道的故障类型可能为 Never,因此它永远不会因错误而终止。然后你打开 .sink:

中的结果
loadTrigger
   .flatMap { params -> AnyPublisher<Result<MyResponse<[Item]>, Error>, Never> in
       WebRepo().index(params: params)
          .map { .success([=11=]) }
          .catch { Just(.failure([=11=])) }
          .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
       }
   }