我正在尝试使用Combine框架创建网络层。我已经实现了一些功能,这些功能运行良好。其中一些api调用需要access token,并且有一些调用来获取和刷新此 token 。现在我面临的问题是,我希望需要此 token 的函数在执行之前自动尝试刷新它-仅在需要刷新或出现错误时才这样做。现在我有这样的功能:

static func login(with parameters: CredentialsRequest) -> AnyPublisher<LoggedUser, RequestError>
static func refreshToken(_ token: Token) -> AnyPublisher<Token, RequestError>
static func activeGames(token: Token) -> AnyPublisher<[Game], RequestError>

token 具有一个属性:requiresRefresh: Bool
我如何使函数activeGames仍然返回AnyPublisher<[Game], RequestError>类型,但以这种方式工作,即如果首先检查 token 是否需要刷新,如果需要,则在刷新之前等待它?

最佳答案

有点离题,但我建议使用Future而不是AnyPublisher,至少是为了更好的语义,因为期货提供了一个单一的价值,就像网络电话一样。

一种方法是取消任何将 token 作为参数接收的函数:

func authenticated<P: Publisher, T>(_ source: @escaping (Token) -> P) -> (Token) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
    return { token in
        let first: AnyPublisher<Token, RequestError>
        if token.requiresRefresh {
            // token needs refresh, let's create the refresh operation
            first = refreshToken(token)
        } else {
            // token doesn't need refresh, just forward it downstream
            first = Result<Token, RequestError>.Publisher(token).eraseToAnyPublisher()
        }
        // now, the Publisher chain
        return first
            .flatMap(source)
            .catch { (error) -> AnyPublisher<T, RequestError> in
                // assuming this error corresponds to the invalid/expired token
                if case .invalidToken = error {
                    // and in this case we redo the authentication and the actual request
                    return refreshToken(token).flatMap(source).eraseToAnyPublisher()
                } else {
                    // report the error otherwise
                    return Fail<T, RequestError>(error: error).eraseToAnyPublisher()
                }
            }.eraseToAnyPublisher()
    }
}

然后,您将像authenticated(activeGames)(token)这样使用它,因此基本上是调用提升函数的结果,而不是直接调用该函数。

参数多于 token 的函数也可以通过上述函数的一些重载来解除:

// for token + another argument
func authenticated<P: Publisher, T, A1>(_ source: @escaping (Token, A1) -> P) -> (Token, A1) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
    return { token, arg1 in
        let src: (A1) -> (Token) -> P = { arg1 in { token in source(token, arg1) } }
        return authenticated(src(arg1))(token)
    }
}

// for token + two other arguments
func authenticated<P: Publisher, T, A1, A2>(_ source: @escaping (Token, A1, A2) -> P) -> (Token, A1, A2) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
    return { token, arg1, arg2 in
        let src: (A1, A2) -> (Token) -> P = { arg1, arg2 in { token in source(token, arg1, arg2) } }
        return authenticated(src(arg1, arg2))(token)
    }
}

一种更简单的解决方案将涉及对最低级别的功能进行操作,例如URLSession堆栈上方的功能,但是制定此解决方案需要了解您的网络堆栈实现。

关于ios - 快速合并-按给定顺序执行任务,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59563141/

10-14 20:59
查看更多