重复的分页请求:
-只需从RXExample https://github.com/ReactiveX/RxSwift/tree/master/RxExample/RxExample/Examples/GitHubSearchRepositories中删除searchBar
-滚动到底部的“ performSearch”时,彼此调用了两次。
预期结果:
滚动到底部仅一次调用“ performSearch”
更新: 移除remove heightForRowAt时解决了此问题。从视图控制器
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
我不知道为什么,但是它在起作用
let tableView: UITableView = self.tableView
let loadNextPageTrigger: (Driver<GitHubSearchState>) -> Signal<()> = { state in
tableView.rx.contentOffset.asDriver()
.withLatestFrom(state)
.flatMap { state in
return tableView.isNearBottomEdge(edgeOffset: 20.0) && !state.shouldLoadNextPage
? Signal.just(())
: Signal.empty()
}
}
let activityIndicator = ActivityIndicator()
let state = githubSearchRepositories(
loadNextPageTrigger: loadNextPageTrigger,
performSearch: { endCursor in
GitHubApiV4.sharedAPI.loadSearchURL(endCursor)
.debug()
.trackActivity(activityIndicator)
})
enum GitHubCommand {
case loadMoreItems
case gitHubResponseReceived(SearchResponse)
}
struct GitHubSearchState {
// control
var shouldLoadNextPage: Bool
var userList: Version<[User]> // Version is an optimization. When something unrelated changes, we don't want to reload table view.
var failure: GitHubServiceError?
var endCursor: String
init() {
shouldLoadNextPage = true
userList = Version([])
failure = nil
endCursor = "Y3Vyc29yOjE="
}
}
此方法包含分页GitHub搜索的要点。
func githubSearchRepositories(
loadNextPageTrigger: @escaping (Driver<GitHubSearchState>) -> Signal<()>,
performSearch: @escaping (String) -> Observable<SearchResponse>
) -> Driver<GitHubSearchState> {
let searchPerformerFeedback: (Driver<GitHubSearchState>) -> Signal<GitHubCommand> = react(
query: { (state) in
GithubQuery(endCursor: state.endCursor, shouldLoadNextPage: state.shouldLoadNextPage)
},
effects: { query -> Signal<GitHubCommand> in
if !query.shouldLoadNextPage {
return Signal.empty()
}
if query.endCursor.isEmpty {
return Signal.just(GitHubCommand.gitHubResponseReceived(.success((users: [], endCursor: ""))))
}
return performSearch(query.endCursor)
.asSignal(onErrorJustReturn: .failure(GitHubServiceError.networkError))
.map(GitHubCommand.gitHubResponseReceived)
})
// this is degenerated feedback loop that doesn't depend on output state
let inputFeedbackLoop: (Driver<GitHubSearchState>) -> Signal<GitHubCommand> = { state in
let loadNextPage = loadNextPageTrigger(state).map { _ in GitHubCommand.loadMoreItems }
return Signal.merge(loadNextPage)
}
// Create a system with two feedback loops that drive the system
// * one that tries to load new pages when necessary
// * one that sends commands from user input
return Driver.system(
initialState: GitHubSearchState.initial,
reduce: GitHubSearchState.reduce,
feedback: searchPerformerFeedback, inputFeedbackLoop
)
}
extension GitHubSearchState {
var isOffline: Bool {
guard let failure = self.failure else {
return false
}
if case .offline = failure {
return true
}
else {
return false
}
}
var isLimitExceeded: Bool {
guard let failure = self.failure else {
return false
}
if case .githubLimitReached = failure {
return true
}
else {
return false
}
}
}
extension GitHubSearchState: Mutable {}
Api
public func loadSearchURL(_ endCursor: String) -> Observable<SearchResponse> {
let urlRequest = generateRequest(cursor: endCursor)
return URLSession.shared
.rx.response(request: urlRequest)
.retry(3)
.observeOn(Dependencies.sharedDependencies.backgroundWorkScheduler)
.map { pair -> SearchResponse in
if pair.0.statusCode == 403 {
return .failure(.githubLimitReached)
}
if !(200 ..< 300 ~= pair.0.statusCode) {
throw exampleError("Call failed")
}
let jsonRoot = JSON(pair.1)
guard let search = jsonRoot["data"]["search"].dictionary else {
throw exampleError("Casting to dictionary failed")
}
guard let nodes = search["nodes"]?.array else {
throw exampleError("Casting to array failed")
}
var userList = [User]()
for node in nodes {
userList.append(User(dict: node))
}
let endCursor = search["pageInfo"]?["endCursor"].string ?? ""
return .success((users: userList, endCursor: endCursor))
}.retryOnBecomesReachable(.failure(.offline), reachabilityService: _reachabilityService)
}