我正在使用一个 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)
在内部send和handleMore函数都调用相同internalSend的request和url,然后调用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 发表评论后,我尝试与多个发布者一起构建所有内容:
mainRequest获得对“主要”请求的响应的发布者。
一个新的urlPublisher,它接收next“主要”或任何后续请求的所有URL。
一个新的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)
但这仍然不起作用......我总是得到“主要”请求的结果。缺少所有后续请求。
看来我自己已经找到了解决办法。
我的想法是,我有一个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. 也许这也是人们从一开始就瞄准的解决方案......
| 归档时间: |
|
| 查看次数: |
1280 次 |
| 最近记录: |