ReactorKit + RxDataSources 列表多次刷新的解决方案
相信使用 ReactorKit
+ RxDataSources
的同学都有遇到列表会多次刷新的问题吧,本篇将提出我的解决方案,相互学习交流
一、常规使用
Reactor
enum Mutation { case setSections([LXFSection]) ... } struct State { var sections : [LXFSection] = [] ... } 复制代码
View
reactor.state.map { $0.sections } .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) 复制代码
由于在 ReactorKit
中,View
对状态的订阅都是针对 State
来说的,而非 State
中的属性,所以只要 State
的值发生改变,View
中所有对 State
的订阅回调都会被调用,从而使视图进行更新。
但是一个属性的变化,理应只需要更新对应的视图即可,要达到这一效果,就需要使用 distinctUntilChanged
这个方法,对于 State
中遵守了 Equatable
协议的属性,直接加上 .distinctUntilChanged()
即可
但是看我们定义的 Section
import RxDataSources enum LXFSection { case list([LXFSectionItem]) } extension LXFSection: SectionModelType { init(original: LXFSection, items: [LXFSectionItem]) { switch original { case .list: self = .list(items) } } var items: [LXFSectionItem] { switch self { case .list(let items): return items } } } enum LXFSectionItem { case item(LXFCellReactor) } 复制代码
需要自己给 LXFSection
遵守 Equatable
并实现协议方法,或者是在 distinctUntilChanged
回调中自己做判断 ,但都太麻烦了~
reactor.state.map { $0.sections } .distinctUntilChanged({ (sectionArr1: [LXFSection], sectionArr2: [LXFSection]) in return 判断 sectionArr1 和 sectionArr2 是否相等 }) .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) 复制代码
那有没有其它办法呢?答案当然是有的
二、优化
创建一个名为 SwiftyTraceableValue
的结构体,定义如下:
public struct SwiftyTraceableValue<T> { public var tracker: Int = 0 public var value: T } 复制代码
内部属性说明:
属性 | 作用 |
---|---|
tracker | 用于判断是否数据是否有发生变化 |
value | 存储原来的值 |
调整 Mutation
enum Mutation { case setSections(TraceableValue<[LXFSection]>) ... } 复制代码
调整 State
struct State { var sections : TraceableValue<[LXFSection]> = .init(value: []) ... } 复制代码
调整 Sections
数据的处理
let oldTracker = self?.currentState.sections.tracker ?? 0 let oldSections = self?.currentState.sections.value ?? [] items = oldSections.first?.items ?? [] + items let finalSections = [LXFSection.list(items)] let sections = TraceableValue(tracker: oldTracker + 1, value: finalSections) let setSections = Observable.just(Mutation.setSections(sections)) 复制代码
调整 View
reactor.state.map { $0.sections } .distinctUntilChanged { $0.tracker == $1.tracker } .map { $0.value } .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) 复制代码
至此,按如上的内容进行调整便可以解决列表多次刷新的问题,但是这改动也忒大了,而且还要自己管理 tracker
的值,这怎么能忍?
那有什么办法可以让我们不再手动管理 tracker
呢?????
这里我们可以使用 Swift
的 Property Wrapper
????
三、改进
我们将 SwiftyTraceableValue
使用 Property Wrapper
进行封装
@propertyWrapper public struct SwiftyTraceableValue<T> { public var tracker: Int = 0 public var value: T public var projectedValue: SwiftyTraceableValue { return self } public var wrappedValue: T { get { return self.value } set { self.tracker += 1 self.value = newValue } } public init(wrappedValue: T) { self.value = wrappedValue } } 复制代码
这样,每次被赋值时即可自动为 tracker
加 1
。这里 projectedValue
我们返回真实类型 SwiftyTraceableValue
,以便后续使用。
这里简单提一下属性包装器的使用,更加具体详细的内容可以看我另一篇文章《Swift - PropertyWrapper》
// 使用 @SwiftyTraceableValue 对属性进行修饰 @SwiftyTraceableValue var sections : [LXFSection] = [] // 获取 wrappedValue 的值 sections // 获取 projectedValue 的值 $sections 复制代码
不仅如此,我们还为 SwiftyTraceableValue
专门扩展 ObservableType
,将进行比较内容部分封装起来
public extension ObservableType { func mapDistinctUntilTraceableValueChanged<T>( _ transform: @escaping (Element) throws -> SwiftyTraceableValue<T> ) -> Observable<T> { return self .map(transform) .distinctUntilChanged { $0.tracker == $1.tracker } .map { $0.value } } } 复制代码
大功造成,接下来看看如何使用吧
四、使用
以下内容是基于第一部分进行调整的,大家可以忘掉第二部分的修改了
最终我们只需要调整两处地方即可解决列表多次刷新的问题!
Reactor
struct State { @SwiftyTraceableValue var sections : [LXFSection] = [] ... } 复制代码
View
reactor.state.mapDistinctUntilTraceableValueChanged { $0.$sections } .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) 复制代码
现在,我将它做成了开源库,方便大家使用,好用的话请 Star
吧 ????
作者:LinXunFeng
链接:https://juejin.cn/post/7039367289170296863