313 lines
10 KiB
Swift
313 lines
10 KiB
Swift
//
|
||
// ListService.swift
|
||
// HealthyZG
|
||
//
|
||
// Created by 林 on 2020/5/29.
|
||
// Copyright © 2020 Lin. All rights reserved.
|
||
//
|
||
|
||
import UIKit
|
||
import RxSwift
|
||
import Moya
|
||
import ObjectMapper
|
||
import RxDataSources
|
||
import RxSwiftExt
|
||
|
||
protocol ListServiceType {
|
||
associatedtype BaseListModel where BaseListModel: Mappable
|
||
associatedtype BaseItem where BaseItem: Mappable
|
||
}
|
||
|
||
typealias PagingTargetTransform = (Int) -> MultiTarget
|
||
|
||
enum PagingRequestType {
|
||
case refresh
|
||
case more
|
||
case clear
|
||
}
|
||
|
||
typealias RefreshResult = (status: RefreshStatus, isEmpty: Bool)
|
||
|
||
/// ListType: 列表数据泛型类,例: ListService<ListModel<PaginationModel>>
|
||
class ListService<ListType>: ListServiceType where ListType: ListModelType, ListType.Item: Mappable {
|
||
typealias BaseListModel = ListType
|
||
typealias BaseItem = ListType.Item
|
||
|
||
struct ListResponse {
|
||
var listModel: BaseListModel?
|
||
var items: [BaseItem] = []
|
||
var pagination: PaginationModel?
|
||
var refreshStatus: RefreshStatus?
|
||
}
|
||
|
||
private var page = 1
|
||
private var isPaging = true
|
||
|
||
var targetTransform: PagingTargetTransform
|
||
var request = PublishSubject<PagingRequestType>()
|
||
var refreshResult: Observable<RefreshResult> {
|
||
let refreshResult = _listResponse.flatMap { listResponse -> Observable<RefreshResult> in
|
||
guard let refreshStatus = listResponse.refreshStatus else {
|
||
return Observable.empty()
|
||
}
|
||
return Observable.just((refreshStatus, listResponse.items.isEmpty))
|
||
}
|
||
return Observable.merge(refreshResult, _refreshResult)
|
||
}
|
||
var listResponse: Observable<ListResponse> {
|
||
return _listResponse
|
||
}
|
||
var response: Observable<[BaseItem]> {
|
||
let itemResponse = _listResponse.map { $0.items }
|
||
return Observable.merge(itemResponse, _localResponse)
|
||
}
|
||
var responseModel: Observable<BaseListModel> {
|
||
return _listResponse.flatMap { listResponse -> Observable<BaseListModel> in
|
||
guard let listModel = listResponse.listModel else {
|
||
return Observable.empty()
|
||
}
|
||
return Observable.just(listModel)
|
||
}
|
||
}
|
||
var pagination: Observable<PaginationModel?> {
|
||
return _listResponse.map { $0.pagination }
|
||
}
|
||
|
||
var error: Observable<Error> {
|
||
return _error.asObserver()
|
||
}
|
||
var executing: Observable<Bool> {
|
||
return _executing.asObserver()
|
||
}
|
||
var isExecuting = false
|
||
var listResponseModel: ListResponse?
|
||
var listModel: BaseListModel? {
|
||
return listResponseModel?.listModel
|
||
}
|
||
var paginationModel: PaginationModel? {
|
||
return listResponseModel?.pagination
|
||
}
|
||
var items: [BaseItem] {
|
||
return listResponseModel?.items ?? []
|
||
}
|
||
|
||
private var _listResponse: Observable<ListResponse> = .empty()
|
||
private let _localResponse = PublishSubject<[BaseItem]>()
|
||
private let _refreshResult = PublishSubject<RefreshResult>()
|
||
private let _error = PublishSubject<Error>()
|
||
private let _executing = PublishSubject<Bool>()
|
||
|
||
// MARK: API
|
||
var fileterMap: ((PagingRequestType) -> FilterMap<Int>) {
|
||
return { [unowned self] request -> FilterMap<Int> in
|
||
switch request {
|
||
case .refresh:
|
||
// 下拉刷新
|
||
self.page = 1
|
||
self.isExecuting = true
|
||
self._executing.onNext(self.isExecuting)
|
||
return .map(self.page)
|
||
case .more:
|
||
// 上拉加载更多
|
||
if self.items.isEmpty {
|
||
self.page = 1
|
||
}
|
||
self.page += 1
|
||
self.isExecuting = true
|
||
self._executing.onNext(self.isExecuting)
|
||
return .map(self.page)
|
||
case .clear:
|
||
// 清空数据
|
||
self.onNext([])
|
||
return .ignore
|
||
}
|
||
}
|
||
}
|
||
var transform: ((Event<BaseListModel>) -> ListResponse) {
|
||
return { [weak self] (event) in
|
||
var listResponse = ListResponse()
|
||
if let items = self?.listResponseModel?.items {
|
||
listResponse.items = items
|
||
}
|
||
guard let self = self else {
|
||
return listResponse
|
||
}
|
||
switch event {
|
||
case let .next(newListModel):
|
||
listResponse.listModel = newListModel
|
||
// New data arrived
|
||
let newItems = newListModel.list
|
||
// Paging enable
|
||
if self.isPaging {
|
||
if self.page == 1 {
|
||
if newItems.isEmpty {
|
||
listResponse.items = []
|
||
} else {
|
||
listResponse.items = newItems
|
||
}
|
||
} else {
|
||
listResponse.items.append(contentsOf: newItems)
|
||
}
|
||
// Paging info
|
||
listResponse.pagination = newListModel.pagination
|
||
if newListModel.pagination!.hasMoreData() {
|
||
listResponse.refreshStatus = .hasMoreData
|
||
} else {
|
||
listResponse.refreshStatus = .noMoreData
|
||
}
|
||
} else {
|
||
if newItems.isEmpty {
|
||
listResponse.items = []
|
||
} else {
|
||
listResponse.items = newItems
|
||
}
|
||
listResponse.refreshStatus = .noMoreData
|
||
}
|
||
default:
|
||
break
|
||
}
|
||
self.listResponseModel = listResponse
|
||
return listResponse
|
||
}
|
||
}
|
||
var filter: ((Event<BaseListModel>) -> Bool) {
|
||
return { [weak self] (event) in
|
||
guard let self = self else {
|
||
return false
|
||
}
|
||
switch event {
|
||
case let .error(error):
|
||
if self.page != 1 {
|
||
self.page -= 1
|
||
}
|
||
self.isExecuting = false
|
||
self._executing.onNext(self.isExecuting)
|
||
self._refreshResult.onNext((.invalidData, self.items.isEmpty))
|
||
self._error.onNext(error)
|
||
return false
|
||
case .completed:
|
||
return false
|
||
case .next:
|
||
self.isExecuting = false
|
||
self._executing.onNext(self.isExecuting)
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
|
||
@available(*, deprecated, message: "将弃用,请使用init(newPaging: Bool = true, targetTransform: @escaping PagingTargetTransform)")
|
||
init(paging: Bool = true, targetTransform: @escaping PagingTargetTransform) {
|
||
self.isPaging = paging
|
||
self.targetTransform = targetTransform
|
||
|
||
_listResponse = request.filterMap(self.fileterMap)
|
||
.map { [weak self] page -> MultiTarget in
|
||
guard let self = self else { return targetTransform(page) }
|
||
return self.targetTransform(page)
|
||
}.flatMapLatest { [weak self] api -> Observable<ListResponse> in
|
||
guard let self = self else {
|
||
return Observable.empty()
|
||
}
|
||
return self.request(api: api)
|
||
.materialize()
|
||
.filter(self.filter)
|
||
.map(self.transform)
|
||
}.share(replay: 1)
|
||
}
|
||
|
||
init(newPaging: Bool = true, targetTransform: @escaping PagingTargetTransform) {
|
||
self.isPaging = newPaging
|
||
self.targetTransform = targetTransform
|
||
|
||
_listResponse = request.filterMap(self.fileterMap)
|
||
.map { [weak self] page -> MultiTarget in
|
||
guard let self = self else { return targetTransform(page) }
|
||
return self.targetTransform(page)
|
||
}.flatMapLatest { [weak self] api -> Observable<ListResponse> in
|
||
guard let self = self else {
|
||
return Observable.empty()
|
||
}
|
||
return self.requestList(api: api)
|
||
.materialize()
|
||
.filter(self.filter)
|
||
.map(self.transform)
|
||
}.share(replay: 1)
|
||
}
|
||
|
||
func request(api: MultiTarget) -> Observable<BaseListModel> {
|
||
if self.isPaging == false {
|
||
return APIProvider.request(token: api)
|
||
.map(ListType.self)
|
||
.asObservable()
|
||
}
|
||
|
||
return APIProvider.request(token: api)
|
||
.map(ListType.self)//, atKeyPath: "result")
|
||
.asObservable()
|
||
}
|
||
|
||
func requestList(api: MultiTarget) -> Observable<BaseListModel> {
|
||
if self.isPaging == false {
|
||
return APIProvider.request(token: api).map(ListType.self).asObservable()
|
||
}
|
||
return APIProvider.request(token: api).map(ListType.self).asObservable()
|
||
}
|
||
|
||
func onNext(_ value: [BaseItem]) {
|
||
listResponseModel?.items = value
|
||
_localResponse.onNext(value)
|
||
}
|
||
|
||
}
|
||
|
||
// MARK: - Map array elements to animatable model
|
||
extension Array where Element: IdentifiableType & Equatable {
|
||
func mapAnimatableSection() -> [AnimatableSectionModel<String, Element>] {
|
||
guard !isEmpty else { return [] }
|
||
let newItems = removedDuplicates()
|
||
return [AnimatableSectionModel(model: "", items: newItems)]
|
||
}
|
||
}
|
||
|
||
extension Array where Element: IdentifiableType {
|
||
func removedDuplicates() -> [Element] {
|
||
// remove duplicated item
|
||
var uuids = Set<AnyHashable>()
|
||
var newItems: [Element] = []
|
||
for item in self {
|
||
let identity = item.identity
|
||
if !uuids.contains(identity) {
|
||
_ = uuids.insert(identity)
|
||
newItems.append(item)
|
||
}
|
||
}
|
||
return newItems
|
||
}
|
||
}
|
||
|
||
// MARK: - Map array elements to section model
|
||
extension Array {
|
||
func mapSection() -> [SectionModel<String, Element>] {
|
||
guard !isEmpty else { return [] }
|
||
return [SectionModel(model: "", items: self)]
|
||
}
|
||
}
|
||
|
||
// MARK: - Differ animatable section model
|
||
extension ListService where BaseItem: IdentifiableType & Equatable {
|
||
var animatableSectionedItems: Observable<[AnimatableSectionModel<String, BaseItem>]> {
|
||
return response.map { items -> [AnimatableSectionModel<String, BaseItem>] in
|
||
return items.mapAnimatableSection()
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Section model
|
||
extension ListService {
|
||
var sectionedItems: Observable<[SectionModel<String, BaseItem>]> {
|
||
return response.map { items -> [SectionModel<String, BaseItem>] in
|
||
return items.mapSection()
|
||
}
|
||
}
|
||
}
|