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
}
}
在我的视图控制器中,我有一个 属性 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
}
}