Swift 合并发布者不会在完成事件后在 flatMap 中触发
Swift Combine publisher won't trigger in flatMap after finished event
我正在尝试在进行网络调用时使用 CurrentValueSubject 创建刷新事件。因此,每当网络请求失败时,我可以按下按钮再次发出请求,但无法使其正常工作,因为失败事件将终止发布者并且它不会再次工作。
import Combine
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: TestViewModel = TestViewModel()
var body: some View {
Button("Test", action: viewModel.test)
Button("Refresh", action: viewModel.refresh)
}
}
class TestViewModel: ObservableObject {
var bag = Set<AnyCancellable>()
private let changeSubject = CurrentValueSubject<Void, Never>(())
func test() {
changeSubject
.flatMap { self.networkPublisher }
.sink(
receiveCompletion: {
switch [=11=] {
case .failure:
print("Failure")
case .finished:
print("Finished")
}
},
receiveValue: {
print("Value: \([=11=])")
}
)
.store(in: &bag)
}
func refresh() {
changeSubject.send(())
}
var networkPublisher: AnyPublisher<String, Error> {
var url = URLRequest(url: URLComponents(string: "www.google.com")!.url!)
url.httpMethod = "GET"
return URLSession.shared
.dataTaskPublisher(for: url)
.tryMap { _ -> String in "Result" }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
为了解决这个问题,我可以使用 .tryCatch { _ -> Just<String> in Just("Error") }
来捕获错误,这样可以防止发布者终止。但是为什么它在发布者终止时不起作用?活动结束后如何工作?
您不应映射到发布者的单个实例,而应映射到一个新实例。您持久化的 networkPublisher 触发其数据任务并完成,但您继续在 flatMap 中映射相同的已完成流。
var networkPublisher
应该是一个计算值,这样您每次传递都会返回一个新的发布者(和一个新的数据任务)。
var networkPublisher: AnyPublisher<String, Error> {
var url = URLRequest(url: URLComponents(string: "www.google.com")!.url!)
url.httpMethod = "GET"
return URLSession.shared
.dataTaskPublisher(for: url)
.tryMap { _ -> String in "Result" }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
看看 Apple 的 this sample code。数据发布者本身是在flatMap的闭包内创建的,强调每次都是一个新的实例。
错误时不终止管道的模式是使用 flatMap
:
捕获错误
changeSubject
.flatMap {
networkPublisher
.catch { _ in Empty() }
}
.sink {
print("Value: \([=10=])")
}
.store(in: &bag)
如您所见,.sink
仅收到 String
的输出和 Never
的错误,因为错误完全在 .flatMap
[=16 内部处理=]
我正在尝试在进行网络调用时使用 CurrentValueSubject 创建刷新事件。因此,每当网络请求失败时,我可以按下按钮再次发出请求,但无法使其正常工作,因为失败事件将终止发布者并且它不会再次工作。
import Combine
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: TestViewModel = TestViewModel()
var body: some View {
Button("Test", action: viewModel.test)
Button("Refresh", action: viewModel.refresh)
}
}
class TestViewModel: ObservableObject {
var bag = Set<AnyCancellable>()
private let changeSubject = CurrentValueSubject<Void, Never>(())
func test() {
changeSubject
.flatMap { self.networkPublisher }
.sink(
receiveCompletion: {
switch [=11=] {
case .failure:
print("Failure")
case .finished:
print("Finished")
}
},
receiveValue: {
print("Value: \([=11=])")
}
)
.store(in: &bag)
}
func refresh() {
changeSubject.send(())
}
var networkPublisher: AnyPublisher<String, Error> {
var url = URLRequest(url: URLComponents(string: "www.google.com")!.url!)
url.httpMethod = "GET"
return URLSession.shared
.dataTaskPublisher(for: url)
.tryMap { _ -> String in "Result" }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
为了解决这个问题,我可以使用 .tryCatch { _ -> Just<String> in Just("Error") }
来捕获错误,这样可以防止发布者终止。但是为什么它在发布者终止时不起作用?活动结束后如何工作?
您不应映射到发布者的单个实例,而应映射到一个新实例。您持久化的 networkPublisher 触发其数据任务并完成,但您继续在 flatMap 中映射相同的已完成流。
var networkPublisher
应该是一个计算值,这样您每次传递都会返回一个新的发布者(和一个新的数据任务)。
var networkPublisher: AnyPublisher<String, Error> {
var url = URLRequest(url: URLComponents(string: "www.google.com")!.url!)
url.httpMethod = "GET"
return URLSession.shared
.dataTaskPublisher(for: url)
.tryMap { _ -> String in "Result" }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
看看 Apple 的 this sample code。数据发布者本身是在flatMap的闭包内创建的,强调每次都是一个新的实例。
错误时不终止管道的模式是使用 flatMap
:
changeSubject
.flatMap {
networkPublisher
.catch { _ in Empty() }
}
.sink {
print("Value: \([=10=])")
}
.store(in: &bag)
如您所见,.sink
仅收到 String
的输出和 Never
的错误,因为错误完全在 .flatMap
[=16 内部处理=]