我有一个设置,用户可以点击一个按钮,通过flatMap启动一系列多个网络请求。我想允许用户通过点击另一个按钮来取消网络请求。对保存的可取消项调用cancel不会取消请求。我有这个班:
class ImageService : ObservableObject, UrlBuildable {
@Published var currentImage: DownloadImageEvent?
private var cancellables = Set<AnyCancellable>()
private var isCancelled: Bool = false
func downloadImage(at: URL) -> AnyPublisher<UIImage, URLError> {
return URLSession.shared.dataTaskPublisher(for: at)
.compactMap { UIImage(data: $0.data) }
.receive(on: RunLoop.main)
.eraseToAnyPublisher()
}
func cancel() {
currentImage = nil
cancellables.removeAll()
self.isCancelled = true
print("(#function) isCancelled: (isCancelled)")
}
typealias DownloadImageEventResult = Result<DownloadImageEvent, URLError>
typealias DownloadImageEventResultPublisher = AnyPublisher<DownloadImageEventResult, Never>
func downloadAllImages() -> DownloadImageEventResultPublisher {
let urls = buildImagesUrls().compactMap{ URL(string: $0) }
let pub: AnyPublisher<URL, Never> = urls.publisher.eraseToAnyPublisher()
var index: Int = 0
let delay: Int = 2
return pub.flatMap(maxPublishers: .max(1)) { [isCancelled] url -> DownloadImageEventResultPublisher in
print("(#function) (url)")
index += 1
// download & create event
print("(#function) isCancelled: (isCancelled)")
if (isCancelled) {
let res = Result<DownloadImageEvent, URLError>.failure(URLError(URLError.Code(rawValue: 404)))
return Just(res).eraseToAnyPublisher()
} else {
return self.downloadImage(at: url)
.print()
.map{ _ in DownloadImageEvent(
index: index,
total: urls.count,
url: url.absoluteString) }
.delay(for: RunLoop.SchedulerTimeType.Stride(TimeInterval(delay)),
scheduler: RunLoop.main)
.convertToResult()
.eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
}
我可以看到,当cancel()
被调用时,isCancelled
被更新,但flatMap
看到了isCancelled
的旧值,因为它最初捕获了它。所以这种方法是行不通的。我曾想过使用私人主题,并让cancel()
向私人主题发送事件,但我如何让flatMap
取消对私人主题来源的事件的响应?
您没有在cancellables
中存储任何内容,因此调用cancellables.removeAll()
不会有任何效果,因为集合为空。为了取消操作,您必须在调用downloadAllImages()
的位置执行此操作。呼叫站点持有令牌,只有在那里您才能取消订阅。