使用 Swift 的 Combine 框架编写 retryIf 运算符
Writing a retryIf operator with Swift's Combine framework
我开始了解 Swift + Swift 的 Combine 框架,想检查一下我尝试实现 retryIf(retries:, shouldRetry:)
运算符是否有意义。特别是,我很好奇是否所有 .eraseToAnyPublisher
都是 expected/idiomatic.
extension Publisher {
func retryIf(retries: Int, shouldRetry: @escaping (Self.Failure) -> Bool) -> AnyPublisher<Self.Output, Self.Failure> {
self.catch { error -> AnyPublisher<Self.Output, Self.Failure> in
guard shouldRetry(error) && retries > 0 else {
return Fail(error: error).eraseToAnyPublisher()
}
return self.retryIf(retries: retries - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
假设所有的AnyPublisher
都没问题,那你什么时候做自己的Publisher
结构呢?例如,常规的 Combine 运算符 retry
returns 是 Retry<Upstream>
结构而不是 AnyPublisher
,但我想您可以按照与上面的代码相同的方式实现它,有些东西喜欢:
extension Publisher {
func doOver(tries: Int) -> AnyPublisher<Self.Output, Self.Failure> {
self.catch { error -> AnyPublisher<Self.Output, Self.Failure> in
guard tries > 0 else { return Fail(error: error).eraseToAnyPublisher() }
return self.doOver(tries: tries - 1).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
您可以通过定义自己的 Publisher
来消除最终的 eraseToAnyPublisher
,从而消除它所需的堆分配。例如:
extension Publisher {
func retry(_ retries: Int, if shouldRetry: @escaping (Failure) -> Bool) -> MyPublishers.RetryIf<Self> {
return .init(upstream: self, triesLeft: retries, shouldRetry: shouldRetry)
}
}
enum MyPublishers { }
extension MyPublishers {
struct RetryIf<Upstream: Publisher>: Publisher {
typealias Output = Upstream.Output
typealias Failure = Upstream.Failure
init(upstream: Upstream, triesLeft: Int, shouldRetry: @escaping (Failure) -> Bool) {
self.upstream = upstream
self.triesLeft = triesLeft
self.shouldRetry = shouldRetry
}
var upstream: Upstream
var triesLeft: Int
var shouldRetry: (Failure) -> Bool
func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input {
upstream
.catch {
triesLeft > 0 && shouldRetry([=10=])
? Self(upstream: upstream, triesLeft: triesLeft - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
: Fail(error: [=10=]).eraseToAnyPublisher()
}
.receive(subscriber: subscriber)
}
}
}
如果你想消除catch
体内的两个eraseToAnyPublisher
调用,你将不得不放弃使用catch
。相反,您将不得不实现自己的 Subscription
。实施 Subscription
要复杂得多,因为它必须是 thread-safe。但是,catch
体内的那些调用只能在上游发生故障的情况下发生,并且每次故障只会发生一次调用。因此,如果上游故障很少见,那么可能不值得付出努力。
我开始了解 Swift + Swift 的 Combine 框架,想检查一下我尝试实现 retryIf(retries:, shouldRetry:)
运算符是否有意义。特别是,我很好奇是否所有 .eraseToAnyPublisher
都是 expected/idiomatic.
extension Publisher {
func retryIf(retries: Int, shouldRetry: @escaping (Self.Failure) -> Bool) -> AnyPublisher<Self.Output, Self.Failure> {
self.catch { error -> AnyPublisher<Self.Output, Self.Failure> in
guard shouldRetry(error) && retries > 0 else {
return Fail(error: error).eraseToAnyPublisher()
}
return self.retryIf(retries: retries - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
假设所有的AnyPublisher
都没问题,那你什么时候做自己的Publisher
结构呢?例如,常规的 Combine 运算符 retry
returns 是 Retry<Upstream>
结构而不是 AnyPublisher
,但我想您可以按照与上面的代码相同的方式实现它,有些东西喜欢:
extension Publisher {
func doOver(tries: Int) -> AnyPublisher<Self.Output, Self.Failure> {
self.catch { error -> AnyPublisher<Self.Output, Self.Failure> in
guard tries > 0 else { return Fail(error: error).eraseToAnyPublisher() }
return self.doOver(tries: tries - 1).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
}
您可以通过定义自己的 Publisher
来消除最终的 eraseToAnyPublisher
,从而消除它所需的堆分配。例如:
extension Publisher {
func retry(_ retries: Int, if shouldRetry: @escaping (Failure) -> Bool) -> MyPublishers.RetryIf<Self> {
return .init(upstream: self, triesLeft: retries, shouldRetry: shouldRetry)
}
}
enum MyPublishers { }
extension MyPublishers {
struct RetryIf<Upstream: Publisher>: Publisher {
typealias Output = Upstream.Output
typealias Failure = Upstream.Failure
init(upstream: Upstream, triesLeft: Int, shouldRetry: @escaping (Failure) -> Bool) {
self.upstream = upstream
self.triesLeft = triesLeft
self.shouldRetry = shouldRetry
}
var upstream: Upstream
var triesLeft: Int
var shouldRetry: (Failure) -> Bool
func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input {
upstream
.catch {
triesLeft > 0 && shouldRetry([=10=])
? Self(upstream: upstream, triesLeft: triesLeft - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
: Fail(error: [=10=]).eraseToAnyPublisher()
}
.receive(subscriber: subscriber)
}
}
}
如果你想消除catch
体内的两个eraseToAnyPublisher
调用,你将不得不放弃使用catch
。相反,您将不得不实现自己的 Subscription
。实施 Subscription
要复杂得多,因为它必须是 thread-safe。但是,catch
体内的那些调用只能在上游发生故障的情况下发生,并且每次故障只会发生一次调用。因此,如果上游故障很少见,那么可能不值得付出努力。