【问题标题】:Alamofire + Combine + MVVM request exampleAlamofire + Combine + MVVM 请求示例
【发布时间】:2020-11-05 13:13:50
【问题描述】:

在我的视图控制器中,我有一个属性 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 重新抛出错误?

【问题讨论】:

    标签: ios swift alamofire combine


    【解决方案1】:

    您可以在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)
    }
    

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


    如果您希望永不失败,但仍将错误作为副作用处理,您可以返回Result,这将捕获值或错误。这样,管道的故障类型可能为Never,因此它永远不会因错误而终止。然后你会在 .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
           }
       }
    

    【讨论】:

    • 关于错误 - 只有一个简单的错误会显示警报,用户可以重试此请求。但是在这种情况下,当错误发生时它会停止订阅,因此用户不能再重试这个请求。
    • @AlexZd,更新了处理错误的可能方法
    • 非常感谢您的帮助。就我而言,与简单的回调方法相比,这种反应式请求方式看起来相当困难。不确定这个例子中的反应性好处,可能是因为它太简单了:)
    • @AlexZd 这只是学习以这种方式思考的问题。你会习惯的(如果你愿意的话)。
    猜你喜欢
    • 2011-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-19
    • 2018-02-10
    相关资源
    最近更新 更多