问题描述
我开始了解Swift + Swift的Combine框架,并想检查一下我实现retryIf(retries:,shouldRetry:)
运算符的尝试是否有意义。特别是,我很好奇所有.erasetoAnyPublisher
是否都是预期的/惯用的。
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
,但是我想您可以按照与上面的代码相同的方式来实现它,例如:>
extension Publisher {
func doOver(tries: Int) -> AnyPublisher<Self.Output,Self.Failure> in
guard tries > 0 else { return Fail(error: error).erasetoAnyPublisher() }
return self.doOver(tries: tries - 1).erasetoAnyPublisher()
}.erasetoAnyPublisher()
}
}
解决方法
您可以通过定义自己的eraseToAnyPublisher
来消除最后的Publisher
,从而消除所需的堆分配。例如:
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)
}
}
}
如果要消除eraseToAnyPublisher
正文中的两个catch
调用,则必须放弃使用catch
。相反,您将必须实现自己的Subscription
。实现Subscription
要复杂得多,因为它必须是线程安全的。但是,catch
主体内的那些调用只能在上游故障的情况下发生,并且每次故障仅发生一个调用。因此,如果上游故障很少发生,那可能就不值得付出努力。