Swift 为什么“publisher.Map”热衷于消费上游价值?
假设我有一个自定义订阅服务器,它在订阅时请求一个值,然后在收到前一个值后三秒钟请求一个附加值:Swift 为什么“publisher.Map”热衷于消费上游价值?,swift,combine,backpressure,Swift,Combine,Backpressure,假设我有一个自定义订阅服务器,它在订阅时请求一个值,然后在收到前一个值后三秒钟请求一个附加值: class MySubscriber: Subscriber { typealias Input = Int typealias Failure = Never private var subscription: Subscription? func receive(subscription: Subscription) { print("Subsc
class MySubscriber: Subscriber {
typealias Input = Int
typealias Failure = Never
private var subscription: Subscription?
func receive(subscription: Subscription) {
print("Subscribed")
self.subscription = subscription
subscription.request(.max(1))
}
func receive(_ input: Int) -> Subscribers.Demand {
print("Value: \(input)")
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
self.subscription?.request(.max(1))
}
return .none
}
func receive(completion: Subscribers.Completion<Never>) {
print("Complete")
subscription = nil
}
}
但是如果我添加了map
操作符,那么MySubscriber
甚至从未收到订阅map
似乎已同步请求了Demand.Unlimited
在收到订阅后,应用程序会无限旋转,因为map
尝试耗尽无限范围:
(1...).publisher
.map { value in
print("Map: \(value)")
return value * 2
}
.subscribe(MySubscriber())
// The `map` transform is executed infinitely with no delay:
//
// Map: 1
// Map: 2
// Map: 3
// ...
我的问题是,map
为什么会这样?我希望map
将其下游需求传递给上游。因为map
应该是用于转换而不是副作用,所以我不理解它当前行为的用例是什么
编辑
我实现了一个版本的map,以显示我认为它应该如何工作:
extension Publishers {
struct MapLazily<Upstream: Publisher, Output>: Publisher {
typealias Failure = Upstream.Failure
let upstream: Upstream
let transform: (Upstream.Output) -> Output
init(upstream: Upstream, transform: @escaping (Upstream.Output) -> Output) {
self.upstream = upstream
self.transform = transform
}
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Upstream.Failure {
let mapSubscriber = Subscribers.LazyMapSubscriber(downstream: subscriber, transform: transform)
upstream.receive(subscriber: mapSubscriber)
}
}
}
extension Subscribers {
class LazyMapSubscriber<Input, DownstreamSubscriber: Subscriber>: Subscriber {
let downstream: DownstreamSubscriber
let transform: (Input) -> DownstreamSubscriber.Input
init(downstream: DownstreamSubscriber, transform: @escaping (Input) -> DownstreamSubscriber.Input) {
self.downstream = downstream
self.transform = transform
}
func receive(subscription: Subscription) {
downstream.receive(subscription: subscription)
}
func receive(_ input: Input) -> Subscribers.Demand {
downstream.receive(transform(input))
}
func receive(completion: Subscribers.Completion<DownstreamSubscriber.Failure>) {
downstream.receive(completion: completion)
}
}
}
extension Publisher {
func mapLazily<Transformed>(transform: @escaping (Output) -> Transformed) -> AnyPublisher<Transformed, Failure> {
Publishers.MapLazily(upstream: self, transform: transform).eraseToAnyPublisher()
}
}
我的猜测是,为发布者定义的map
的超负荷。Sequence
使用某种快捷方式来提高性能。这对于无限序列来说是中断的,但即使对于有限序列,不管下游需求如何,都会急切地耗尽序列,这会扰乱我的直觉。在我看来,以下代码:
(1...3).publisher
.map { value in
print("Map: \(value)")
return value * 2
}
.subscribe(MySubscriber())
应打印:
Subscribed
Map: 1
Value: 2
Map: 2
Value: 4
Map: 3
Value: 6
Complete
而是打印:
Map: 1
Map: 2
Map: 3
Subscribed
Value: 2
Value: 4
Value: 6
Complete
下面是一个简单的测试,它不涉及任何自定义订阅者:
(1...).publisher
//.map { $0 }
.flatMap(maxPublishers: .max(1)) {
(i:Int) -> AnyPublisher<Int,Never> in
Just<Int>(i)
.delay(for: 3, scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
.sink { print($0) }
.store(in: &storage)
果然,它解决了这个问题!通过对map操作符隐藏序列发布器,我们可以防止优化。您使用的是什么Xcode和什么目标平台?在macOS 10.15.4上使用Xcode 11.4,我无法重现您的问题。使用您的
MySubscriber
,以下管道运行正常:(1…3).publisher.print(“Empty/map”).map{dump($0)}.print(“map/subscriber”).subscribe(MySubscriber())
哦,抱歉,我刚刚意识到我复制了错误的代码。我尝试使用无限范围1…
,而不是1…3
(我在测试时将其设为有限范围)。我刚刚更新了要修复的问题。为了回答您的问题,我仍然使用10.14.6版本的Xcode 11.3.1。我刚刚在运行macOS 10.15.2和Xcode 11.4的个人MacBook上进行了测试,我看到了相同的行为。我开始以不同的方式编写一个反例,并最终确认了您的结果!好,提交:FB7660502
。我希望你不介意我在报告中使用了你的简单示例。相反,我对此非常高兴。嘿,我想到了一个简单的解决方法。将其添加到答案中。它证实了你的假设。另外,这里有一个挂起的非常简短的测试用例:(1…).publisher.map{$0}.prefix(1).sink{{uuu}
@robmayoff我想可能是publisher.Sequence是一个只用于测试的玩具。
Map: 1
Map: 2
Map: 3
Subscribed
Value: 2
Value: 4
Value: 6
Complete
(1...).publisher
//.map { $0 }
.flatMap(maxPublishers: .max(1)) {
(i:Int) -> AnyPublisher<Int,Never> in
Just<Int>(i)
.delay(for: 3, scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
.sink { print($0) }
.store(in: &storage)
(1...).publisher.eraseToAnyPublisher()
.map { $0 }
// ...