使用Swift的Combine框架编写retryIf运算符 [英] Writing a retryIf operator with Swift's Combine framework
问题描述
我开始了解Swift + Swift的Combine框架,并想检查我实现 retryIf(retries :, shouldRetry:)
运算符的尝试是否有意义.特别是,我很好奇所有 .eraseToAnyPublisher
是否都是预期的/惯用的.
I'm getting to know Swift + Swift's Combine framework and wanted to check that my attempt at implementing a retryIf(retries:, shouldRetry:)
operator makes sense. In particular, I'm curious if all the .eraseToAnyPublisher
s are 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
返回一个 Retry< Upstream>
结构而不是一个 AnyPublisher
结构,但是我想您可以沿与上面的代码相同的行,类似:
Assuming that all the AnyPublisher
s are ok, when do you want to make your own Publisher
struct? For example, the regular Combine operator retry
returns a Retry<Upstream>
struct rather than an AnyPublisher
, but I imagine you could implement it along the same lines as the code above, something like:
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 ,从而消除所需的堆分配.>.例如:
You can eliminate the final eraseToAnyPublisher
, and thus the heap allocation it requires, by defining your own Publisher
. For example:
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($0)
? Self(upstream: upstream, triesLeft: triesLeft - 1, shouldRetry: shouldRetry).eraseToAnyPublisher()
: Fail(error: $0).eraseToAnyPublisher()
}
.receive(subscriber: subscriber)
}
}
}
如果要消除 catch
正文中的两个 eraseToAnyPublisher
调用,则必须放弃使用 catch
.相反,您将必须实现自己的 Subscription
.实现 Subscription
要复杂得多,因为它必须是线程安全的.但是, catch
主体内的那些调用只能在上游故障的情况下发生,并且每次故障仅发生一次调用.因此,如果上游故障很少发生,那可能就不值得付出努力.
If you want to eliminate the two eraseToAnyPublisher
calls inside the catch
body, you will have to give up using catch
. Instead you will have to implement your own Subscription
. Implementing Subscription
is much more complicated, because it has to be thread-safe. However, those calls inside the catch
body can only happen in the case of an upstream failure, and only one of the calls happens per failure. So if upstream failures are rare, it's probably not worth the effort.
这篇关于使用Swift的Combine框架编写retryIf运算符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!