ReactorKit + RxDataSources 列表多次刷新的解决方案

5,568 阅读2分钟

image.png

欢迎关注微信公众号:FSA全栈行动 👋

相信使用 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 呢?🤔

这里我们可以使用 SwiftProperty 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
    }
}

这样,每次被赋值时即可自动为 tracker1。这里 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 吧 😃

GitHub: SwiftyTraceableValue