使用Swift的Combine框架编写retryIf运算符 [英] Writing a retryIf operator with Swift's Combine framework

查看:76
本文介绍了使用Swift的Combine框架编写retryIf运算符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我开始了解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 .eraseToAnyPublishers 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 AnyPublishers 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆