什么组合运算符/方法可用于加载“分页 API”的所有页面?

pd9*_*d95 5 ios swift combine

我正在使用一个 Web API,它提供的结果达到给定的限制(pageSize请求的参数)。如果结果数量超过此限制,则响应消息会预先填充一个 URL,可以向该 URL 发出后续请求以获取更多结果。如果有更多结果,则再次以相同方式指示。

我的目的是一次获取所有结果。


目前我有类似以下的请求和响应结构:

// Request structure
struct TvShowsSearchRequest {
    let q: String
    let pageSize: Int?
}

// Response structure
struct TvShowsSearchResponse: Decodable {
    let next: String?
    let total : Int
    let searchTerm : String
    let searchResultListShow: [SearchResult]?
}
Run Code Online (Sandbox Code Playgroud)

使用完成处理程序解决“旧式”问题时,我必须编写一个处理程序,它使用响应的 URL 触发“处理更多”请求:

func handleResponse(request: TvShowsSearchRequest, result: Result<TvShowsSearchResponse, Error>) -> Void {
    switch result {
    case .failure(let error):
        fatalError(error.localizedDescription)
    case .success(let value):
        print("> Total number of shows matching the query: \(value.total)")
        print("> Number of shows fetched: \(value.searchResultListShow?.count ?? 0)")

        if let moreUrl = value.next {
            print("> URL to fetch more entries \(moreUrl)")

            // start recursion here: a new request, calling the same completion handler...
            dataProvider.handleMore(request, nextUrl: moreUrl, completion: handleResponse)
        }
    }
}

let request = TvShowsSearchRequest(query: "A", pageSize: 50)
dataProvider.send(request, completion: handleResponse)
Run Code Online (Sandbox Code Playgroud)

在内部sendhandleMore函数都调用相同internalSendrequesturl,然后调用URLSession.dataTask(...),检查 HTTP 错误,解码响应并调用完成块。


现在我想使用组合框架并使用自动提供分页响应的发布者,而无需调用另一个发布者。

因此,我编写了一个requestPublisher函数,它接受request和(初始)url并返回一个URLSession.dataTaskPublisher检查 HTTP 错误(使用tryMap),decode响应。

现在我必须确保发布者在最后发出的值具有有效nextURL 并且完成事件发生时自动“更新”自己。

我发现有一种Publisher.append方法可以完全做到这一点,但到目前为止我遇到的问题是:我只想在特定条件下追加(=valid next)。

下面的伪代码说明了它:

func requestPublisher(for request: TvShowsSearchRequest, with url: URL) -> AnyPublisher<TvShowsSearchResponse, Error> {
    // ... build urlRequest, skipped here ...

    let apiCall = self.session.dataTaskPublisher(for: urlRequest)
        .tryMap { data, response -> Data in
            guard let httpResponse = response as? HTTPURLResponse else {
                throw APIError.server(message: "No HTTP response received")
            }
            if !(200...299).contains(httpResponse.statusCode) {
                throw APIError.server(message: "Server respondend with status: \(httpResponse.statusCode)")
            }
            return data
        }
        .decode(type: TvShowsSearchResponse.self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
    return apiCall
}


// Here I'm trying to use the Combine approach

var moreURL : String?

dataProvider.requestPublisher(request)
    .handleEvents(receiveOutput: {
        moreURL = $0.next   // remember the "next" to fetch more data
    })
    .append(dataProvider.requestPublisher(request, next: moreURL))  // this does not work, because moreUrl was not prepared at the time of creation!!
    .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
    .store(in: &cancellableSet)
Run Code Online (Sandbox Code Playgroud)

我想有些人已经以一种被动的方式解决了这个问题。每当我找到可行的解决方案时,它都会再次涉及递归。我不认为这是一个正确的解决方案应该是什么样子。

我正在寻找一个发送响应的发布者,而我没有提供回调函数。可能必须有使用 Publisher of Publishers 的解决方案,但我还不了解它。


在@kishanvekariya 发表评论后,我尝试与多个发布者一起构建所有内容:

  1. mainRequest获得对“主要”请求的响应的发布者。

  2. 一个新的urlPublisher,它接收next“主要”或任何后续请求的所有URL。

  3. 一个新的moreRequest发布者,它正在获取urlPublisher新请求的每个值,将所有nextURL发送回urlPublisher.

然后我尝试将moreRequest发布者附加到mainRequest with append

var urlPublisher = PassthroughSubject<String, Error>()

var moreRequest = urlPublisher
    .flatMap {
        return dataProvider.requestPublisher(request, next: $0)
            .handleEvents(receiveOutput: {
                if let moreURL = $0.next {
                    urlPublisher.send(moreURL)
                }
            })
    }

var mainRequest = dataProvider.requestPublisher(request)
    .handleEvents(receiveOutput: {
        if let moreURL = $0.next {
            urlPublisher.send(moreURL)
        }
    })
    .append(moreRequest)
    .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
    .store(in: &cancellableSet)
Run Code Online (Sandbox Code Playgroud)

但这仍然不起作用......我总是得到“主要”请求的结果。缺少所有后续请求。

pd9*_*d95 5

看来我自己已经找到了解决办法。

我的想法是,我有一个urlPublisher使用第一个 URL 初始化的 which,然后执行该nextURL,并可能向该提供一个 URL urlPublisher,并通过这样做引起后续请求。

let url = endpoint(for: request)     // initial URL
let urlPublisher = CurrentValueSubject<URL, Error>(url)

urlPublisher
    .flatMap {
        return dataProvider.requestPublisher(for: request, with: $0)
            .handleEvents(receiveOutput: {
                if let next = $0.next, let moreURL = URL(string: self.transformNextUrl(nextUrl: next)) {
                    urlPublisher.send(moreURL)
                } else {
                    urlPublisher.send(completion: .finished)
                }
            })
    }
    .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) })
    .store(in: &cancellableSet)
Run Code Online (Sandbox Code Playgroud)

所以最后,我使用了两个发布者的组合,flatMap而不是非功能性的append. 也许这也是人们从一开始就瞄准的解决方案......