Swift - 排队合并请求

时间:2021-02-26 13:18:01

标签: ios swift combine

我正在处理一个组合请求,我想在某个事件之后执行或排队执行。下面是场景-

  1. 生成新请求。
  2. 检查应用是否有访问令牌
  3. 如果是,则执行请求
  4. 如果没有,获取令牌,然后执行请求

下面是我的 API,其中每个请求都会被触发 -

public func fetchData<T: Codable>(to request: URLRequest) -> AnyPublisher<Result<T>, Error> {
    
    if hasToken {
        return self.urlSession.dataTaskPublisher(for: request)
            .tryMap(self.parseJson)
            .receive(on: RunLoop.main)
            .subscribe(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    else {
        // Store request somewhere
        // Get token
        // Execute stored request
    }
}

如果有人能建议我如何处理代码的其他部分,我将不胜感激。

1 个答案:

答案 0 :(得分:2)

如果快速连续发生多个请求,您开始使用的方法将不起作用。所有这些请求都会将 hasToken 视为 false,并且它们都会发起令牌请求。

您可以创建一个 var tokenRequested: Bool 属性并同步对其的访问。

我处理这个问题的方法 - 不确定这是否是最好的方法 - 是创建一个管道,所有请求都将通过该管道排队:

class Service {
    
    private let requestToken = PassthroughSubject<Void, Error>()
    private let tokenSubject = CurrentValueSubject<Token?, Error>(nil)

    var c: Set<AnyCancellable> = []
    
    init() {
        requestToken.zip(tokenSubject)
            .flatMap { (_, token) -> AnyPublisher<Token, Error> in
                if let token = token {
                    return Just(token).setFailureType(to: Error.self)
                                      .eraseToAnyPublisher()
                } else {
                    return self.fetchToken()
                               .eraseToAnyPublisher()
                }
            }
            .map { $0 as Token? }
            .subscribe(tokenSubject)
            .store(in: &c)
    }
   
    private func fetchToken() -> AnyPublisher<Token, Error> {
        // async code to fetch the token
    }
}

通过 requestToken 的任何请求都与 tokenSubject 中的值同步,因此第一个请求与初始 nil 一起使用,但后续请求等待发布下一个值,这发生在上一个完成。

然后,要发出请求,您首先要获取令牌 getToken(),然后发出请求。

extension Service {
    private func getToken() -> AnyPublisher<Token, Error> {
        // request token, which starts queues it in the pipeline
        requestToken.send(())

        // wait until next token is available
        return tokenSubject
            .compactMap { $0 } // non-nil token
            .first() // only one
            .eraseToAnyPublisher()
    }

    func fetchData<T: Codable>(request: URLRequest) -> AnyPublisher<T, Error> {
        getToken()
            .flatMap { token in
                // make request
            }
            .eraseToAnyPublisher()
    }
}