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
吧 😃
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08